Routing is essential for any CRUD application. Refine's headless architecture allows you to use any router solution, without being locked into a specific router/framework.
Refine also offers built-in router integrations for the most popular frameworks such as React Router, Next.js and Remix.
These integrations makes it easier to use Refine with these frameworks and offers a lot of benefits such as:
Automatic parameter detection in hooks/components.
Automatic redirections after mutation or authentication.
Set of utility components & hooks which can be used to navigate between pages/routes.
Since Refine is router agnostic, you are responsible for creating your own routes.
If you are using React Router, you'll be defining your routes under the Routes component.
If you are using Next.js, you'll be defining your routes in the pages or app directory.
If you are using Remix, you'll be defining your routes in the app/routes directory.
To integrate a router provider with Refine, all you need to do is to import the router integration of your choice and pass it to the <Refine />'s routerProvider prop.
Refine is able to work on React Native apps and with the help of the community package @refinenative/expo-router, you can use Refine's routing features on React Native as well.
Once you passed router provider to <Refine /> component, you can use all the features of Refine in a same way, regardless of your application's framework/router.
Relationship Between Resources and Routes
Check the guide
Please check the guide for more information on this topic.
importReactfrom"react";import{useGo,useShow}from"@refinedev/core";exportconstProductShow: React.FC = ()=>{// We're inferring the resource and the id from the route params// So we can call useShow hook without any arguments.// const result = useShow({ resource: "products", id: "xxx" })constresult = useShow();const{queryResult:{data,isLoading},} = result;constgo = useGo();if(isLoading)return<div>Loading...</div>;return(<><div><h1>{data?.data?.name}</h1><p>Material: {data?.data?.material}</p><small>ID: {data?.data?.id}</small></div><buttononClick={()=>{go({to:{resource:"products",action:"list",},});}}>
Go to Products list
</button></>);};
Content: import React from "react";
import { Refine } from "@refinedev/core";
import routerProvider from "@refinedev/react-router-v6";
import dataProvider from "@refinedev/simple-rest";
import { BrowserRouter, Route, Routes } from "react-router-dom";
import "./style.css";
import { ProductList } from "./pages/products/list.tsx";
import { ProductShow } from "./pages/products/show.tsx";
export default function App() {
return (
<BrowserRouter>
<Refine
routerProvider={routerProvider}
dataProvider={dataProvider("https://api.fake-rest.refine.dev")}
resources={[
{
name: "products",
// We're defining the routes and assigning them to an action of a resource
list: "/my-products",
show: "/my-products/:id",
// For sake of simplicity, we are not defining other routes here but the implementation is the same
// create: "/my-products/new",
// edit: "/my-products/:id/edit",
// clone: "/my-products/:id/clone",
},
]}
>
<Routes>
<Route path="/my-products" element={<ProductList />} />
<Route path="/my-products/:id" element={<ProductShow />} />
</Routes>
</Refine>
</BrowserRouter>
);
}
File: /style.css
Content: html {
margin: 0;
padding: 0;
}
body {
margin: 0;
padding: 12px;
}
* {
box-sizing: border-box;
}
body {
font-family: sans-serif;
}
form label, form input, form button {
display: block;
width: 100%;
margin-bottom: 6px;
}
span + button {
margin-left: 6px;
}
ul > li {
margin-bottom: 6px;
}
File: /pages/products/list.tsx
Content: import React from "react";
import { useGo, useList } from "@refinedev/core";
export const ProductList: React.FC = () => {
// We're inferring the resource from the route
// So we call `useList` hook without any arguments.
// const { ... } = useList({ resource: "products" })
const { data, isLoading } = useList();
const go = useGo();
if (isLoading) return <div>Loading...</div>;
return (
<ul>
{data?.data?.map((product) => (
<li key={product.id}>
<span>{product.name}</span>
<button
onClick={() => {
go({
to: {
resource: "products",
action: "show",
id: product.id,
},
});
}}
>
show
</button>
</li>
))}
</ul>
);
};
File: /pages/products/show.tsx
Content: import React from "react";
import { useGo, useShow } from "@refinedev/core";
export const ProductShow: React.FC = () => {
// We're inferring the resource and the id from the route params
// So we can call useShow hook without any arguments.
// const result = useShow({ resource: "products", id: "xxx" })
const result = useShow();
const {
queryResult: { data, isLoading },
} = result;
const go = useGo();
if (isLoading) return <div>Loading...</div>;
return (
<>
<div>
<h1>{data?.data?.name}</h1>
<p>Material: {data?.data?.material}</p>
<small>ID: {data?.data?.id}</small>
</div>
<button
onClick={() => {
go({
to: {
resource: "products",
action: "list",
},
});
}}
>
Go to Products list
</button>
</>
);
};
importReactfrom"react";import{useGo,useShow}from"@refinedev/core";constProductShow = ()=>{// We're inferring the resource and the id from the route params// So we can call useShow hook without any arguments.// const result = useShow({ resource: "products", id: "xxx" })constresult = useShow();const{queryResult:{data,isLoading},} = result;constgo = useGo();if(isLoading)return<div>Loading...</div>;return(<><div><h1>{data?.data?.name}</h1><p>Material: {data?.data?.material}</p><small>ID: {data?.data?.id}</small></div><buttononClick={()=>{go({to:{resource:"products",action:"list"}});}}>
Go to Products list
</button></>);};exportdefaultProductShow;
Dependencies:
Code Files
File: /pages/_app.tsx
Content: import React from "react";
import { Refine } from "@refinedev/core";
import routerProvider from "@refinedev/nextjs-router/pages";
import dataProvider from "@refinedev/simple-rest";
import type { AppProps } from "next/app";
import "../style.css";
function App({ Component, pageProps }: AppProps) {
return (
<Refine
routerProvider={routerProvider}
dataProvider={dataProvider("https://api.fake-rest.refine.dev")}
resources={[
{
name: "products",
// We're defining the routes and assigning them to an action of a resource
list: "/my-products",
show: "/my-products/:id",
// For sake of simplicity, we are not defining other routes here but the implementation is the same
// create: "/my-products/new",
// edit: "/my-products/:id/edit",
// clone: "/my-products/:id/clone",
},
]}
>
<Component {...pageProps} />
</Refine>
);
}
export default App;
File: /style.css
Content: html {
margin: 0;
padding: 0;
}
body {
margin: 0;
padding: 12px;
}
* {
box-sizing: border-box;
}
body {
font-family: sans-serif;
}
form label,
form input,
form button {
display: block;
width: 100%;
margin-bottom: 6px;
}
span + button {
margin-left: 6px;
}
ul > li {
margin-bottom: 6px;
}
File: /pages/my-products/index.tsx
Content: import React from "react";
import { useGo, useList } from "@refinedev/core";
const ProductList = () => {
// We're inferring the resource from the route
// So we call `useList` hook without any arguments.
// const { ... } = useList({ resource: "products" })
const { data, isLoading } = useList();
const go = useGo();
if (isLoading) return <div>Loading...</div>;
return (
<ul>
{data?.data?.map((product) => (
<li key={product.id}>
<span>{product.name}</span>
<button
onClick={() => {
go({
to: {
resource: "products",
action: "show",
id: product.id,
},
});
}}
>
show
</button>
</li>
))}
</ul>
);
};
export default ProductList;
File: /pages/my-products/[id].tsx
Content: import React from "react";
import { useGo, useShow } from "@refinedev/core";
const ProductShow = () => {
// We're inferring the resource and the id from the route params
// So we can call useShow hook without any arguments.
// const result = useShow({ resource: "products", id: "xxx" })
const result = useShow();
const {
queryResult: { data, isLoading },
} = result;
const go = useGo();
if (isLoading) return <div>Loading...</div>;
return (
<>
<div>
<h1>{data?.data?.name}</h1>
<p>Material: {data?.data?.material}</p>
<small>ID: {data?.data?.id}</small>
</div>
<button
onClick={() => {
go({ to: { resource: "products", action: "list" } });
}}
>
Go to Products list
</button>
</>
);
};
export default ProductShow;
importReactfrom"react";import{useGo,useShow}from"@refinedev/core";constProductShow = ()=>{// We're inferring the resource and the id from the route params// So we can call useShow hook without any arguments.// const result = useShow({ resource: "products", id: "xxx" })constresult = useShow();const{queryResult:{data,isLoading},} = result;constgo = useGo();if(isLoading)return<div>Loading...</div>;return(<><div><h1>{data?.data?.name}</h1><p>Material: {data?.data?.material}</p><small>ID: {data?.data?.id}</small></div><buttononClick={()=>{go({to:{resource:"products",action:"list"}});}}>
Go to Products list
</button></>);};exportdefaultProductShow;
Content: import React from "react";
import {
Links,
LiveReload,
Meta,
Outlet,
Scripts,
ScrollRestoration,
} from "@remix-run/react";
import { Refine } from "@refinedev/core";
import routerProvider from "@refinedev/remix-router";
import dataProvider from "@refinedev/simple-rest";
export default function App() {
return (
<html lang="en">
<head>
<Meta />
<Links />
</head>
<body>
<Refine
routerProvider={routerProvider}
dataProvider={dataProvider("https://api.fake-rest.refine.dev")}
resources={[
{
name: "products",
// We're defining the routes and assigning them to an action of a resource
list: "/my-products",
show: "/my-products/:id",
// For sake of simplicity, we are not defining other routes here but the implementation is the same
// create: "/my-products/new",
// edit: "/my-products/:id/edit",
// clone: "/my-products/:id/clone",
},
]}
>
<Outlet />
</Refine>
<ScrollRestoration />
<Scripts />
<LiveReload />
</body>
</html>
);
}
File: /app/routes/my-products._index.tsx
Content: import { useGo, useList } from "@refinedev/core";
import React from "react";
const ProductList = () => {
// We're inferring the resource from the route
// So we call `useList` hook without any arguments.
// const { ... } = useList({ resource: "products" })
const { data, isLoading } = useList();
const go = useGo();
if (isLoading) return <div>Loading...</div>;
return (
<ul>
{data?.data?.map((product) => (
<li key={product.id}>
<span>{product.name}</span>
<button
onClick={() => {
go({
to: {
resource: "products",
action: "show",
id: product.id,
},
});
}}
>
show
</button>
</li>
))}
</ul>
);
};
export default ProductList;
File: /app/routes/my-products.$id.tsx
Content: import React from "react";
import { useGo, useShow } from "@refinedev/core";
const ProductShow = () => {
// We're inferring the resource and the id from the route params
// So we can call useShow hook without any arguments.
// const result = useShow({ resource: "products", id: "xxx" })
const result = useShow();
const {
queryResult: { data, isLoading },
} = result;
const go = useGo();
if (isLoading) return <div>Loading...</div>;
return (
<>
<div>
<h1>{data?.data?.name}</h1>
<p>Material: {data?.data?.material}</p>
<small>ID: {data?.data?.id}</small>
</div>
<button
onClick={() => {
go({ to: { resource: "products", action: "list" } });
}}
>
Go to Products list
</button>
</>
);
};
export default ProductShow;
Router integration of Refine allows you to use useForm without passing resource, id and action parameters.
It will also redirect you to resource's action route defined in redirect prop. redirect prop is list by default.
Additionally, router integrations exposes an <UnsavedChangesNotifier /> component which can be used to notify the user about unsaved changes before navigating away from the current page. This component provides this feature which can be enabled by setting warnWhenUnsavedChanges to true in useForm hooks.
React Router v6
Next.js
Remix
app.tsx
import{Refine}from"@refinedev/core"; import{ routerProvider, UnsavedChangesNotifier, }from"@refinedev/react-router-v6"; import{BrowserRouter,Routes}from"react-router-dom"; constApp=()=>( <BrowserRouter> <Refine // ... routerProvider={routerProvider} options={{ warnWhenUnsavedChanges:true, }} > <Routes>{/* ... */}</Routes> {/* The `UnsavedChangesNotifier` component should be placed under <Refine /> component. */} <UnsavedChangesNotifier/> </Refine> </BrowserRouter> );
A router integration of Refine consists of a set of basic implementations for:
Ability to navigate between pages/routes
An interface to interact with the parameters and query strings of the current route
An utility to navigate back in the history
A simple component to use for anchor tags
These implementations will be provided via routerProvider which expects an object with the following methods:
go: A function that accepts an object and returns a function that handles the navigation.
back: A function that returns a function that handles the navigation back in the history.
parse: A function that returns a function that parses the current route and returns an object.
Link: A React component that accepts a to prop and renders a component that handles the navigation to the given to prop.
While all these methods are optional, if you're working on creating a custom router integration, you'll be able to incrementally add more features and adopt more of Refine's features by implementing more of these methods.