Introduction
Now we've learned the data fetching essentials and basics of authentication in Refine. In this unit, we'll learn how to add a router provider to our app and the features we're unlocking with a router provider.
Refine provides integrations for the most popular routing options such as React Router, Next.js and Remix.
It's recommended to pick a built-in integration for your router but if you want to use a custom solution you can create your own provider with Refine's easy to use router provider interface.
Refine won't interfere with your router's way of handling navigation. You'll be generating the routes/pages as you would normally do with your router.
Providing a router provider to Refine will unlock many features without giving up any of your router's features.
This unit will cover the following topics:
- Refine's resource concept and how to use it,
- Using router integration to infer parameters such as
resource
,action
andid
from the URL, - Handling navigation and redirections in Refine,
- Using router integration to store form and table states in the URL,
- Finally, handling authentication with router options.
This unit will be UI framework agnostic. Related parts of routing for the UI frameworks will be covered in the next units.
Adding Router Provider
Let's get started with adding our dependencies. For routing, we will use react-router-dom
, and to integrate it with Refine, we'll be using @refinedev/react-router-v6
package.
- npm
- pnpm
- yarn
npm i react-router-dom @refinedev/react-router-v6
pnpm add react-router-dom @refinedev/react-router-v6
yarn add react-router-dom @refinedev/react-router-v6
Then we'll pass our router provider to the <Refine />
component. Additionally, we'll be wrapping our app with <BrowserRouter />
from react-router-dom
.
Update your src/App.tsx
file by adding the following lines:
import { Refine, Authenticated } from "@refinedev/core";
import routerProvider from "@refinedev/react-router-v6";
import { BrowserRouter } from "react-router-dom";
import { dataProvider } from "./providers/data-provider";
import { authProvider } from "./providers/auth-provider";
import { ShowProduct } from "./pages/products/show";
import { EditProduct } from "./pages/products/edit";
import { ListProducts } from "./pages/products/list";
import { CreateProduct } from "./pages/products/create";
import { Login } from "./pages/login";
import { Header } from "./components/header";
export default function App(): JSX.Element {
return (
<BrowserRouter>
<Refine
dataProvider={dataProvider}
authProvider={authProvider}
routerProvider={routerProvider}
>
<Authenticated key="protected" fallback={<Login />}>
<Header />
{/* <ShowProduct /> */}
{/* <EditProduct /> */}
<ListProducts />
{/* <CreateProduct /> */}
</Authenticated>
</Refine>
</BrowserRouter>
);
}
Now we're ready to start exploring the features of Refine's router integration.
In the next step, we'll be learning about how to inform Refine about the related routes per resource.
import { useTable, useMany } from "@refinedev/core"; export const ListProducts = () => { const { tableQuery: { data, isLoading }, current, setCurrent, pageCount, sorters, setSorters, } = useTable({ resource: "protected-products", pagination: { current: 1, pageSize: 10 }, sorters: { initial: [{ field: "id", order: "asc" }] }, }); const { data: categories } = useMany({ resource: "categories", ids: data?.data?.map((product) => product.category?.id) ?? [], }); if (isLoading) { return <div>Loading...</div>; } const onPrevious = () => { if (current > 1) { setCurrent(current - 1); } }; const onNext = () => { if (current < pageCount) { setCurrent(current + 1); } }; const onPage = (page: number) => { setCurrent(page); }; const getSorter = (field: string) => { const sorter = sorters?.find((sorter) => sorter.field === field); if (sorter) { return sorter.order; } } const onSort = (field: string) => { const sorter = getSorter(field); setSorters( sorter === "desc" ? [] : [ { field, order: sorter === "asc" ? "desc" : "asc", }, ] ); } const indicator = { asc: "⬆️", desc: "⬇️" }; return ( <div> <h1>Products</h1> <table> <thead> <tr> <th onClick={() => onSort("id")}> ID {indicator[getSorter("id")]} </th> <th onClick={() => onSort("name")}> Name {indicator[getSorter("name")]} </th> <th> Category </th> <th onClick={() => onSort("material")}> Material {indicator[getSorter("material")]} </th> <th onClick={() => onSort("price")}> Price {indicator[getSorter("price")]} </th> </tr> </thead> <tbody> {data?.data?.map((product) => ( <tr key={product.id}> <td>{product.id}</td> <td>{product.name}</td> <td> { categories?.data?.find( (category) => category.id == product.category?.id, )?.title } </td> <td>{product.material}</td> <td>{product.price}</td> </tr> ))} </tbody> </table> <div className="pagination"> <button type="button" onClick={onPrevious}> {"<"} </button> <div> {current - 1 > 0 && <span onClick={() => onPage(current - 1)}>{current - 1}</span>} <span className="current">{current}</span> {current + 1 < pageCount && <span onClick={() => onPage(current + 1)}>{current + 1}</span>} </div> <button type="button" onClick={onNext}> {">"} </button> </div> </div> ); };