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>
    );
};