On this page
article
Refine
With Refine, the custom logic for querying the API is done in the Data Provider.
Boilerplate
Data Provider
appState using Zustand.
This structure uses a backend server and an api key to make all requests. API url and API key are stored
with Zustand. Zustand uses hooks, which doesn’t work with auth-provider, so we use getState to access it there.
import type { DataProvider } from "@refinedev/core";
import appState from "./appState";
function getHeaders(token: string) {
const headers = new Headers()
headers.append("Authorization", `Bearer ${token}`)
headers.append("Accept", "application/vnd.api+json")
return headers
}
export const genericDataProvider: DataProvider = {
getOne: async ({resource, id, meta }) => {
const apiUrl = appState.getState().apiUrl
const apiToken = appState.getState().apiToken
const response = await fetch(`${apiUrl}/${resource}/${id}`, {
method: 'GET',
headers: getHeaders(apiToken),
} as RequestInit)
if (response.status != 200) { throw response; }
const data = await response.json()
return data.data
},
update: async ({ resource, id, variables }) => {
const apiUrl = appState.getState().apiUrl
const apiToken = appState.getState().apiToken
const headers = getHeaders(apiToken)
headers.append("Content-Type", "application/vnd.api+json")
const response = await fetch(`${apiUrl}/${resource}/${id}`, {
method: "PATCH",
headers,
body: JSON.stringify({ data: {type: resource, attributes: variables } })
})
if (response.status != 200) { throw response; }
const data = await response.json()
return data.data
},
getList: async ({ resource, pagination, filters, sorters, meta }) => {
const apiUrl = appState.getState().apiUrl
const apiToken = appState.getState().apiToken
const response = await fetch(`${apiUrl}/${resource}`, {
method: "GET",
headers: getHeaders(apiToken),
})
if (response.status != 200) { throw response; }
const data = await response.json()
return data.data
},
create: async ({ resource, variables }) => {
const apiUrl = appState.getState().apiUrl
const apiToken = appState.getState().apiToken
const headers = getHeaders(apiToken)
headers.append("Content-Type", "application/vnd.api+json")
const response = await fetch(`${apiUrl}/${resource}`, {
method: "POST",
headers,
body: JSON.stringify({ data: {type: resource, attributes: variables } })
})
if (response.status != 200) { throw response; }
const data = await response.json()
return data.data
},
deleteOne: () => {
throw new Error("Not implemented");
},
getApiUrl: () => {
return appState.getState().apiUrl
},
// Optional methods:
// getMany: () => { /* ... */ },
// createMany: () => { /* ... */ },
// deleteMany: () => { /* ... */ },
// updateMany: () => { /* ... */ },
// custom: () => { /* ... */ },
};
Auth Provider
This takes the login form and does all auth actions on it. In this case, we just take the server url and API key and use that to determine if the authentication is valid. Not neccesary if authentication is handled another way.
import { AuthProvider } from "@refinedev/core";
import appState from "./appState";
function getHeaders() {
const headers = new Headers()
headers.append("Accept", "application/vnd.api+json")
return headers
}
export const authProvider: AuthProvider = {
check: async () => {
// When logging in, we'll obtain an access token from our API and store it in the local storage.
// Now let's check if the token exists in the local storage.
// In the later steps, we'll be implementing the login and logout methods.
const apiToken = appState.getState().apiToken
return { authenticated: Boolean(apiToken) };
},
login: async ({ apiToken, domain }) => {
const setApiToken = appState.getState().setApiToken
const setApiUrl = appState.getState().setApiUrl
const headers = getHeaders()
headers.set("Authorization", `Bearer ${apiToken}`)
const response = await fetch(`${domain}/v1/me`, {headers})
if(response.status === 200) {
setApiToken(apiToken)
setApiUrl(`${domain}/v1`)
return {success: true}
}
return {success: false, redirectTo: "/login"}
},
logout: async () => {
const setApiToken = appState.getState().setApiToken
const setApiUrl = appState.getState().setApiUrl
setApiToken("")
setApiUrl("")
return { success: true, redirectTo: "/login" }
},
onError: async (error) => {
if(error?.status === 401) {
return {
logout: true,
error: { message: error.message }
}
}
},
// optional methods
register: async (params) => { throw new Error("Not implemented"); },
forgotPassword: async (params) => { throw new Error("Not implemented"); },
updatePassword: async (params) => { throw new Error("Not implemented"); },
getIdentity: async () => {
const apiToken = appState.getState().apiToken
const apiUrl = appState.getState().apiUrl
const headers = getHeaders()
headers.set("Authorization", `Bearer ${apiToken}`)
const response = await fetch(`${apiUrl}/me`, { headers })
if(response.status !== 200) { return null }
return (await response.json()).data.attributes
},
getPermissions: async () => { throw new Error("Not implemented"); },
};
App page
This defines the different crud parts
function App() {
return (
<BrowserRouter>
<RefineKbarProvider>
<DevtoolsProvider>
<Refine
dataProvider={keygenShProvider}
authProvider={authProvider}
routerProvider={routerProvider}
resources={[
{
name: "products",
list: `/${routes.pages.products}`,
show: `/${routes.pages.products}/:id`,
edit: `/${routes.pages.products}/:id/edit`,
create: `/${routes.pages.products}/create`,
meta: { label: "Products"},
}
]}
>
<Routes>
<Route element={
<Authenticated key={"protected-routes"} redirectOnFail={"/login"}>
<Header />
<Outlet />
</Authenticated>
}>
<Route index element={<NavigateToResource resource={routes.pages.products} />} />
<Route path={routes.pages.products}>
<Route index element={<ListProducts />} />
<Route path={":id"} element={<ShowProduct />} />
<Route path={":id/edit"} element={<EditProduct />} />
<Route path={"create"} element={<CreateProduct />} />
</Route>
</Route>
<Route element={
<Authenticated key={"protected-pages"} fallback={<Outlet />}>
<NavigateToResource resource={routes.pages.products} />
</Authenticated>
}>
<Route path={"/login"} element={<Login />} />
</Route>
</Routes>
</Refine>
<DevtoolsPanel />
</DevtoolsProvider>
</RefineKbarProvider>
</BrowserRouter>
);
}
CRUD
Create will take in a form and submit it
Create
export const CreateProduct = () => {
// const { onFinish, mutation } = useForm({
// action: "create",
// resource: "products",
// });
const { onFinish, mutation } = useForm({ redirect: "edit"});
const { options } = useSelect({
resource: "categories",
// optionLabel: "title", // Default value is "title" so we don't need to provide it.
// optionValue: "id", // Default value is "id" so we don't need to provide it.
});
const onSubmit = (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
// Using FormData to get the form values and convert it to an object.
const data = Object.fromEntries(new FormData(event.target).entries());
// Calling onFinish to submit with the data we've collected from the form.
onFinish({
...data,
// price: Number(data.price).toFixed(2),
distributionStrategy: data.distributionStrategy.toString().toUpperCase(),
// category: { id: Number(data.category) },
});
};
return (
<form onSubmit={onSubmit}>
<label htmlFor="name">Name</label>
<input type="text" id="name" name="name" />
<label htmlFor="distributionStrategy">Distribution Strategy</label>
<textarea id="distributionStrategy" name="distributionStrategy" />
<label htmlFor="url">Url</label>
<input type="text" id="url" name="url" />
{mutation.isSuccess && <span>successfully submitted!</span>}
<button type="submit">Submit</button>
</form>
);
};