Custom create function for a resource

I have a resource who's data provider is supabase. That's fine for read and edit. But the create logic is entirely custom. I want to press a button, hit an api endpoint that will generate the new record(s), but stay on the page and show the success (or failure) of the generation, hopefully using a notification.
14 Replies
kapa.ai
kapa.ai4mo ago
Hi @oshiri3676. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave: ---------------------- You can customize the create function for a resource in your data provider. In your case, you can modify the create method in your data provider to hit your custom API endpoint. Here's an example of how you can do this:
export const dataProvider = (apiUrl: string): DataProvider => ({
// ...
create: async ({ resource, variables }) => {
const url = `${apiUrl}/your-custom-endpoint`;

const { data } = await axiosInstance.post(url, variables);

return {
data,
};
},
// ...
});
export const dataProvider = (apiUrl: string): DataProvider => ({
// ...
create: async ({ resource, variables }) => {
const url = `${apiUrl}/your-custom-endpoint`;

const { data } = await axiosInstance.post(url, variables);

return {
data,
};
},
// ...
});
(Source: Refine Documentation (https://refine.dev/docs/tutorial/understanding-dataprovider/create-dataprovider#create)) Then, you can use the useCreate hook in your component to call this create method. You can customize the success and error notifications using the successNotification and errorNotification props respectively. Here's an example:
const { mutate } = useCreate();

mutate({
resource: "your-resource",
values: {
// your values here
},
successNotification: (data, values, resource) => {
return {
message: `${data.title} Successfully fetched.`,
description: "Success with no errors",
type: "success",
};
},
errorNotification: (data, values, resource) => {
return {
message: `Something went wrong when getting ${data.id}`,
description: "Error",
type: "error",
};
},
});
const { mutate } = useCreate();

mutate({
resource: "your-resource",
values: {
// your values here
},
successNotification: (data, values, resource) => {
return {
message: `${data.title} Successfully fetched.`,
description: "Success with no errors",
type: "success",
};
},
errorNotification: (data, values, resource) => {
return {
message: `Something went wrong when getting ${data.id}`,
description: "Error",
type: "error",
};
},
});
(Source: Refine Documentation (https://refine.dev/docs/data/hooks/use-create#successnotification), Refine Documentation (https://refine.dev/docs/data/hooks/use-create#errornotification)) Please note that the NotificationProvider is required for the successNotification and errorNotification props to work. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai4mo ago
<@783439443410813008> kapa.ai is still learning and improving, please let us know how it did by reacting below
fascinating-indigo
fascinating-indigo4mo ago
@kapa.ai - How would my custom dataProvider fall back to the provided supabase dataProvider for all other use cases?
kapa.ai
kapa.ai4mo ago
Let me check the knowledge base for you. One moment! :books: ---------------------- You can create a custom data provider that wraps the Supabase data provider and overrides the create method for your specific resource. For all other methods and resources, it will fall back to the Supabase data provider. Here's an example:
import { dataProvider as supabaseDataProvider } from "@refinedev/supabase";
import { supabaseClient } from "utility";
import { DataProvider } from "@refinedev/core";

const supabaseProvider = supabaseDataProvider(supabaseClient);

const customDataProvider: DataProvider = {
...supabaseProvider,
create: async ({ resource, variables }) => {
if (resource === "your-resource") {
// Your custom create logic here
} else {
return supabaseProvider.create({ resource, variables });
}
},
// other methods...
};
import { dataProvider as supabaseDataProvider } from "@refinedev/supabase";
import { supabaseClient } from "utility";
import { DataProvider } from "@refinedev/core";

const supabaseProvider = supabaseDataProvider(supabaseClient);

const customDataProvider: DataProvider = {
...supabaseProvider,
create: async ({ resource, variables }) => {
if (resource === "your-resource") {
// Your custom create logic here
} else {
return supabaseProvider.create({ resource, variables });
}
},
// other methods...
};
In this example, the create method checks if the resource is the one you want to customize. If it is, it runs your custom logic. If it's not, it falls back to the create method of the Supabase data provider. You can then use customDataProvider as your data provider in your app:
import { Refine } from "@refinedev/core";

function App() {
return (
<Refine dataProvider={customDataProvider}>
{/* ... */}
</Refine>
);
}

export default App;
import { Refine } from "@refinedev/core";

function App() {
return (
<Refine dataProvider={customDataProvider}>
{/* ... */}
</Refine>
);
}

export default App;
This way, you can customize the behavior for specific resources or methods while keeping the default behavior of the Supabase data provider for everything else. (Source: Refine Documentation (https://refine.dev/docs/data/packages/supabase#register-supabase-data-provider)) ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai4mo ago
<@783439443410813008> kapa.ai is still learning and improving, please let us know how it did by reacting below
fascinating-indigo
fascinating-indigo4mo ago
@kapa.ai In my case, the custom create logic will actual be another "api" endpoint within the same app. But I am using remix, so I'm not sure how to structure that endpoint. I guess the real generation would be in the loader() function. But what should the main component return?
kapa.ai
kapa.ai4mo ago
Let me check the knowledge base for you. One moment! :books: ---------------------- In Remix, you can define a route that will handle your custom create logic. The loader function in this route will be responsible for calling your API endpoint and generating the new record(s). Here's an example of how you can structure your route:
// app/routes/generate.tsx

import { json, LoaderFunction } from 'remix';
import { generateRecord } from '../utils'; // replace with your actual function

export let loader: LoaderFunction = async ({ request }) => {
try {
const newRecord = await generateRecord(); // replace with your actual logic
return json(newRecord, { status: 200 });
} catch (error) {
return json({ error: error.message }, { status: 500 });
}
};

export default function GenerateRoute() {
// This component will not be rendered because the loader function does not return any data to it.
// You can return null or a loading state here.
return null;
}
// app/routes/generate.tsx

import { json, LoaderFunction } from 'remix';
import { generateRecord } from '../utils'; // replace with your actual function

export let loader: LoaderFunction = async ({ request }) => {
try {
const newRecord = await generateRecord(); // replace with your actual logic
return json(newRecord, { status: 200 });
} catch (error) {
return json({ error: error.message }, { status: 500 });
}
};

export default function GenerateRoute() {
// This component will not be rendered because the loader function does not return any data to it.
// You can return null or a loading state here.
return null;
}
In this example, generateRecord is a function that calls your API endpoint and generates the new record(s). The loader function calls generateRecord and returns the new record(s) in the response. If there's an error, it returns the error message in the response. The default export of the route is a React component that will be rendered when the route is visited. However, since the loader function does not return any data to this component, it will not be rendered. You can return null or a loading state from this component. Then, in your create component, you can call this route when the create button is clicked:
import { useMutation } from 'react-query';
import { fetch } from '@remix-run/data';

function CreateComponent() {
const mutation = useMutation(() => fetch('/generate'));

return (
<button onClick={() => mutation.mutate()}>
Create
</button>
);
}
import { useMutation } from 'react-query';
import { fetch } from '@remix-run/data';

function CreateComponent() {
const mutation = useMutation(() => fetch('/generate'));

return (
<button onClick={() => mutation.mutate()}>
Create
</button>
);
}
In this example, useMutation from react-query is used to call the /generate route when the create button is clicked. fetch from @remix-run/data is used to make the request to the /generate route. Please replace generateRecord and /generate with your actual function and route. (Source: Remix Documentation (https://remix.run/docs/en/v1/api/remix#loader)) ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai4mo ago
<@783439443410813008> kapa.ai is still learning and improving, please let us know how it did by reacting below
fascinating-indigo
fascinating-indigo4mo ago
How can i tie that into notifications, so the user of the button gets some feedback about success versus failure? @kapa.ai How can i tie that into notifications, so the user of the button gets some feedback about success versus failure?
kapa.ai
kapa.ai4mo ago
Let me check the knowledge base for you. One moment! :books: ---------------------- You can use Refine's useNotification hook to display success or error notifications based on the result of the mutation. Here's how you can modify the CreateComponent to do this:
import { useMutation } from 'react-query';
import { fetch } from '@remix-run/data';
import { useNotification } from '@refinedev/core';

function CreateComponent() {
const { open } = useNotification();
const mutation = useMutation(() => fetch('/generate'), {
onSuccess: () => {
open({
type: "success",
message: "Record successfully generated",
description: "The record was successfully generated",
});
},
onError: (error) => {
open({
type: "error",
message: "Failed to generate record",
description: `Error: ${error.message}`,
});
},
});

return (
<button onClick={() => mutation.mutate()}>
Create
</button>
);
}
import { useMutation } from 'react-query';
import { fetch } from '@remix-run/data';
import { useNotification } from '@refinedev/core';

function CreateComponent() {
const { open } = useNotification();
const mutation = useMutation(() => fetch('/generate'), {
onSuccess: () => {
open({
type: "success",
message: "Record successfully generated",
description: "The record was successfully generated",
});
},
onError: (error) => {
open({
type: "error",
message: "Failed to generate record",
description: `Error: ${error.message}`,
});
},
});

return (
<button onClick={() => mutation.mutate()}>
Create
</button>
);
}
In this example, onSuccess and onError callbacks are provided to the useMutation hook. These callbacks are called when the mutation is successful or fails, respectively. Inside these callbacks, the open function from useNotification is called to display a success or error notification. Please note that the NotificationProvider is required for the useNotification hook to work. You can provide it to the Refine component in your app:
import { Refine } from "@refinedev/core";
import { notificationProvider } from "@refinedev/mantine"; // replace with your actual notification provider

function App() {
return (
<Refine notificationProvider={notificationProvider}>
{/* ... */}
</Refine>
);
}

export default App;
import { Refine } from "@refinedev/core";
import { notificationProvider } from "@refinedev/mantine"; // replace with your actual notification provider

function App() {
return (
<Refine notificationProvider={notificationProvider}>
{/* ... */}
</Refine>
);
}

export default App;
In this example, notificationProvider from @refinedev/mantine is used as the notification provider. Please replace it with your actual notification provider. (Source: Refine Documentation (https://refine.dev/docs/guides-concepts/notifications#notification-providers)) ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai4mo ago
<@783439443410813008> kapa.ai is still learning and improving, please let us know how it did by reacting below
fascinating-indigo
fascinating-indigo4mo ago
@kapa.ai I don't understand why you're having me use the 'useMutation' hook from 'react-query' instead of refine's 'useQuery'. I need to make sure that my custom dataProvider will be invoked (when this particular resource hits a 'create' event). Will that happen with useMutation vs useQuery?
kapa.ai
kapa.ai4mo ago
Let me check the knowledge base for you. One moment! :books: ---------------------- I apologize for the confusion. You're correct. In the context of Refine, you should use Refine's useCreate hook to ensure that your custom data provider's create method is invoked. The useMutation hook from react-query is a more general-purpose hook and doesn't automatically tie into Refine's data provider logic. Here's how you can modify the CreateComponent to use the useCreate hook:
import { useCreate, useNotification } from "@refinedev/core";

function CreateComponent() {
const { mutate } = useCreate();
const { open } = useNotification();

const handleCreate = async () => {
try {
await mutate({
resource: "your-resource",
values: {
// your values here
},
successNotification: {
message: "Record successfully generated",
description: "The record was successfully generated",
type: "success",
},
errorNotification: {
message: "Failed to generate record",
description: "An error occurred while generating the record",
type: "error",
},
});
} catch (error) {
open({
type: "error",
message: "Failed to generate record",
description: `Error: ${error.message}`,
});
}
};

return <button onClick={handleCreate}>Create</button>;
}
import { useCreate, useNotification } from "@refinedev/core";

function CreateComponent() {
const { mutate } = useCreate();
const { open } = useNotification();

const handleCreate = async () => {
try {
await mutate({
resource: "your-resource",
values: {
// your values here
},
successNotification: {
message: "Record successfully generated",
description: "The record was successfully generated",
type: "success",
},
errorNotification: {
message: "Failed to generate record",
description: "An error occurred while generating the record",
type: "error",
},
});
} catch (error) {
open({
type: "error",
message: "Failed to generate record",
description: `Error: ${error.message}`,
});
}
};

return <button onClick={handleCreate}>Create</button>;
}
In this example, the mutate method from the useCreate hook is used to create a new record. The successNotification and errorNotification props are used to display a success or error notification when the record is successfully created or if an error occurs, respectively. Please replace "your-resource" and the values object with your actual resource and values. (Source: Refine Documentation (https://refine.dev/docs/data/hooks/use-create)) ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai4mo ago
<@783439443410813008> kapa.ai is still learning and improving, please let us know how it did by reacting below