Router Provider
refine needs some router functions to create resource pages, navigate, etc. This provider allows you to use the router library you want.
A router provider must include the following methods:
const routerProvider = {
useHistory: () => {
push: (...args) => any,
replace: (...args) => any,
goBack: (...args) => any,
},
useLocation: () => {
pathname: string,
search: string,
},
useParams: <Params extends { [K in keyof Params]?: string } = {}>() => Params,
Prompt: React.FC<PromptProps*>,
Link: React.FC<any>,
RouterComponent?: React.FC<any>,
};
*: Too see →<PromptProps>
INFORMATION
refine includes many out-of-the-box router providers to use in your projects like
TIP
We do not recommend creating this provider unless you do not need any customization on the router. Instead, you can use Next.js Router for your Next.js app and React Router V6, React Router V5 or React Location for your react app.
Usage
To activate router provider in refine, we have to pass the routerProvider to the <Refine /> component.
- React Router V6
- React Router V5
- React Location
- Next.js Router
- Remix Router
react-router-v6
import { Refine } from "@pankod/refine-core";
import routerProvider from "@pankod/refine-react-router-v6";
const App: React.FC = () => {
return <Refine routerProvider={routerProvider} />;
};
react-router
import { Refine } from "@pankod/refine-core";
import routerProvider from "@pankod/refine-react-router-v5";
const App: React.FC = () => {
return <Refine routerProvider={routerProvider} />;
};
react-location
import { Refine } from "@pankod/refine-core";
import routerProvider from "@pankod/refine-react-location";
const App: React.FC = () => {
return <Refine routerProvider={routerProvider} />;
};
nextjs
import { Refine } from "@pankod/refine-core";
import routerProvider from "@pankod/refine-nextjs-router";
import { AppProps } from "next/app";
function MyApp({ Component, pageProps }: AppProps): JSX.Element {
return (
<Refine routerProvider={routerProvider}>
<Component {...pageProps} />
</Refine>
);
}
remix
import type { MetaFunction } from "@remix-run/node";
import {
Links,
LiveReload,
Meta,
Outlet,
Scripts,
ScrollRestoration,
} from "@remix-run/react";
import { Refine } from "@pankod/refine-core";
import routerProvider from "@pankod/refine-remix-router";
export const meta: MetaFunction = () => ({
charset: "utf-8",
title: "New Remix + Refine App",
viewport: "width=device-width,initial-scale=1",
});
export default function App() {
return (
<html lang="en">
<head>
<Meta />
<Links />
</head>
<body>
<Refine routerProvider={routerProvider}>
<Outlet />
</Refine>
<ScrollRestoration />
<Scripts />
<LiveReload />
</body>
</html>
);
}
Creating a router provider
The routerProvider methods refine expects are exactly the same as React Router V6 methods.
To understand how to create a routerProvider, let's examine how the React Router V6, React Router V5, React Location and Next.js Router libraries provided by refine create a routerProvider.
useHistory
refine uses push, replace, and goBack functions of useHistory for navigation.
- React Router V6
- React Router V5
- React Location
- Next.js Router
- Remix Router
react-useHistory-v6
import { IRouterProvider } from "@pankod/refine-core";
const routerProvider: IRouterProvider = {
...
useHistory: () => {
const navigate = useNavigate();
return {
push: navigate,
replace: (path: string) => {
navigate(path, { replace: true });
},
goBack: () => {
navigate(-1);
},
};
},
...
};
react-useHistory
import { IRouterProvider } from "@pankod/refine-core";
import { useHistory } from "react-router-dom";
const routerProvider: IRouterProvider = {
...
useHistory,
...
};
react-location-useHistory
import { IRouterProvider } from "@pankod/refine-core";
import { useHistory, useLocation } from "react-location";
const routerProvider: IRouterProvider = {
...
useHistory: () => {
const navigate = useNavigate();
const location = useLocation();
return {
push: (path: string) => {
navigate({
to: path,
});
},
replace: (path: string) => {
navigate({
to: path,
replace: true,
});
},
goBack: () => {
location.history.back();
},
};
},
...
};
nextjs-useHistory
import { IRouterProvider } from "@pankod/refine-core";
import { useRouter } from "next/router";
const routerProvider: IRouterProvider = {
...
useHistory: () => {
const router = useRouter();
const { push, replace, back } = router;
return {
push,
replace,
goBack: back,
};
},
...
};
remix-useHistory
import { IRouterProvider } from "@pankod/refine-core";
import { useNavigate } from "@remix-run/react";
const routerProvider: IRouterProvider = {
...
useHistory: () => {
const navigate = useNavigate();
return {
push: navigate,
replace: (path: string) => {
navigate(path, { replace: true });
},
goBack: () => {
navigate(-1);
},
};
},
...
};
useLocation
refine uses the pathname to find the location of the user and search to find the query string.
- React Router V6
- React Router V5
- React Location
- Next.js Router
- Remix Router
react-useLocation-v6
import { IRouterProvider } from "@pankod/refine-core";
import { useLocation } from "react-router-dom";
const routerProvider: IRouterProvider = {
...
useLocation,
...
};
react-useLocation
import { IRouterProvider } from "@pankod/refine-core";
import { useLocation } from "react-router-dom";
const routerProvider: IRouterProvider = {
...
useLocation,
...
};
react-location-useLocation
import { IRouterProvider } from "@pankod/refine-core";
import { useLocation } from "react-location";
const routerProvider: IRouterProvider = {
...
useLocation: () => {
const location = useLocation();
return {
pathname: location.current.pathname,
search: location.current.searchStr,
};
},
...
};
nextjs-useLocation
import { IRouterProvider } from "@pankod/refine-core";
import { useRouter } from "next/router";
import qs from "qs";
const routerProvider: IRouterProvider = {
...
useLocation: () => {
const router = useRouter();
const { pathname, query } = router;
const queryParams = qs.stringify(query);
return {
pathname,
search: queryParams && `?${queryParams}`,
};
},
...
};
remix-useLocation
import { IRouterProvider } from "@pankod/refine-core";
import { useLocation } from "@remix-run/react";
const routerProvider: IRouterProvider = {
...
useLocation: () => {
const location = useLocation();
return location;
},
...
};
useParams
refine uses useParams to use action name, record id, etc. found in the route.
- React Router V6
- React Router V5
- React Location
- Next.js Router
- Remix Router
react-router-v6-useParams
import { IRouterProvider } from "@pankod/refine-core";
import { useParams } from "react-router-dom";
const routerProvider: IRouterProvider = {
...
useParams,
...
};
react-router-v5-useParams
import { IRouterProvider } from "@pankod/refine-core";
import { useParams } from "react-router-dom";
const routerProvider: IRouterProvider = {
...
useParams,
...
};
react-location-useParams
import { IRouterProvider } from "@pankod/refine-core";
import { useMatch } from "react-location";
const routerProvider: IRouterProvider = {
...
useParams: () => {
const { params } = useMatch();
return params as any;
},
...
};
nextjs-useParams
import { IRouterProvider } from "@pankod/refine-core";
import { useRouter } from "next/router";
const routerProvider: IRouterProvider = {
...
useParams: <Params>() => {
const router = useRouter();
const { query } = router;
return query as unknown as Params;
},
...
};
remix-useParams
import { handleUseParams, IRouterProvider } from "@pankod/refine-core";
import { useParams } from "@remix-run/react";
const routerProvider: IRouterProvider = {
...
useParams: () => {
const params = useParams();
return handleUseParams(params);
},
...
};
Prompt
refine uses <Prompt> to display the alert when warnWhenUnsavedChanges is true.
- React Router V6
- React Router V5
- React Location
- Next.js Router
- Remix Router
react--router-v6-prompt
import { useEffect, useContext } from "react";
import { UNSAFE_NavigationContext as NavigationContext } from "react-router-dom";
import type { History } from "history";
import type { PromptProps } from "@pankod/refine-core";
export const Prompt: React.FC<PromptProps> = ({
message,
when,
setWarnWhen,
}) => {
const navigator = useContext(NavigationContext).navigator as History;
useEffect(() => {
if (!when) return;
const unblock = navigator.block((transition: any) => {
if (window.confirm(message)) {
setWarnWhen?.(false);
unblock();
transition.retry();
} else {
navigator.location.pathname = window.location.pathname;
}
});
return unblock;
}, [when, location, message]);
return null;
};
import { IRouterProvider } from "@pankod/refine-core";
import { Prompt } from "./prompt";
const routerProvider: IRouterProvider = {
...
Prompt,
...
};
react--router-v5-prompt
import { IRouterProvider } from "@pankod/refine-core";
import { Prompt } from "react-router-dom";
const routerProvider: IRouterProvider = {
...
Prompt: Prompt as any,
...
};
react-location-prompt
import { useEffect } from "react";
import { useLocation } from "react-location";
import type { PromptProps } from "@pankod/refine-core";
export const Prompt: React.FC<PromptProps> = ({
message,
when,
setWarnWhen,
}) => {
const location = useLocation();
useEffect(() => {
if (!when) return;
const unblock = location.history.block((transition) => {
if (window.confirm(message)) {
setWarnWhen?.(false);
unblock();
transition.retry();
} else {
location.current.pathname = window.location.pathname;
}
});
return unblock;
}, [when, location, message]);
return null;
};
import { IRouterProvider } from "@pankod/refine-core";
import { Prompt } from "./prompt";
const routerProvider: IRouterProvider = {
...
Prompt,
...
};
nextjs-prompt
import { useRouter } from "next/router";
import { useEffect } from "react";
import type { PromptProps } from "@pankod/refine-core";
export const Prompt: React.FC<PromptProps> = ({
message,
when,
setWarnWhen,
}) => {
const router = useRouter();
useEffect(() => {
const routeChangeStart = () => {
if (when) {
const allowTransition = window.confirm(message);
if (allowTransition) {
setWarnWhen?.(false);
} else {
router.events.emit("routeChangeError");
throw "Abort route change due to unsaved changes prompt. Ignore this error.";
}
}
};
router.events.on("routeChangeStart", routeChangeStart);
return () => router.events.off("routeChangeStart", routeChangeStart);
}, [when]);
return null;
};
import { IRouterProvider } from "@pankod/refine-core";
import { Prompt } from "./prompt";
const routerProvider: IRouterProvider = {
...
Prompt,
...
};
remix-prompt
import { useEffect, useContext } from "react";
import { UNSAFE_NavigationContext as NavigationContext } from "react-router-dom";
import type { History } from "history";
import type { PromptProps } from "@pankod/refine-core";
export const Prompt: React.FC<PromptProps> = ({
message,
when,
setWarnWhen,
}) => {
const navigator = useContext(NavigationContext).navigator as History;
useEffect(() => {
if (!when) return;
const unblock = navigator.block((transition: any) => {
if (window.confirm(message)) {
setWarnWhen?.(false);
unblock();
transition.retry();
} else {
navigator.location.pathname = window.location.pathname;
}
});
return unblock;
}, [when, message]);
return null;
};
import { IRouterProvider } from "@pankod/refine-core";
import { Prompt } from "./prompt";
const routerProvider: IRouterProvider = {
...
Prompt,
...
};
Link
refine uses <Link> for navigation.
- React Router V6
- React Router V5
- React Location
- Next.js Router
- Remix Router
react-router-v6-link
import { IRouterProvider } from "@pankod/refine-core";
import { Link } from "react-router-dom";
const routerProvider: IRouterProvider = {
...
Link,
...
};
react-router-v5-link
import { IRouterProvider } from "@pankod/refine-core";
import { Link } from "react-router-dom";
const routerProvider: IRouterProvider = {
...
Link,
...
};
react-location-link
import { IRouterProvider } from "@pankod/refine-core";
import { Link } from "react-location";
const routerProvider: IRouterProvider = {
...
Link,
...
};
nextjs-link
import React from "react";
import Link, { LinkProps } from "next/link";
type MakeOptional<Type, Key extends keyof Type> = Omit<Type, Key> &
Partial<Pick<Type, Key>>;
type RefineLinkProps =
| (MakeOptional<LinkProps, "href"> & {
to: LinkProps["href"];
})
| LinkProps;
export const RefineLink: React.FC<RefineLinkProps> = ({
children,
...props
}) => (
<Link
href={"to" in props ? props.to : props.href}
legacyBehavior={false}
{...props}
>
{children}
</Link>
);
import { IRouterProvider } from "@pankod/refine-core";
import { RefineLink } from "./refineLink";
const routerProvider: IRouterProvider = {
...
Link: RefineLink,
...
};
INFORMATION
We use <WrapperLink> instead of using <Link> directly because refine uses <Link> component with to prop in its packages. So <WrapperLink> maps to to href prop.
@pankod/refine-nextjs-router uses <Link/> component with legacyBehavior prop set to false by default to comply with the new <Link/> behavior of Next.js which is currently under experimental flag but soon to be the default behavior with Next.js 13.
To learn more about the changing behavior of <Link/> check out this PR
remix-link
import { IRouterProvider } from "@pankod/refine-core";
import { Link } from "@remix-run/react";
const routerProvider: IRouterProvider = {
...
Link,
...
};
routes
routes allow us to create custom pages in your react apps that have different paths than those defined by resources.
Refer to the Custom Pages documentation for detailed information. →
INFORMATION
Since Nextjs and Remix has a file system based router built on the page concept, you can create your custom pages under the pages folder you don't need routes property.
RouterComponent
It creates the navigation routes of the refine app and determines how the components will be rendered on which paths.
In general, we can list what it does as follows:
- It creates create, edit, list, show pages with paths according to the resources' own name.
- Allows rendering of custom
routespassed torouterProvidersas properties. - Different routes render when the user is authenticated and not.
INFORMATION
RouterComponent is required for refine React apps but not required for Next.js and Remix apps.
Since Next.js and Remix has a folder base route structure, it is used by exporting the <NextRouteComponent> or <RemixRouteComponent> from the created page.
→ Refer to the React Router V6's <RouterComponent> for detailed usage information.
→ Refer to the React Router V5's <RouterComponent> for detailed usage information.
→ Refer to the React Location's <RouterComponent> for detailed usage information.
→ Refer to the Next.js Router's <NextRouteComponent> for detailed usage information.
→ Refer to the Next.js Router's <RemixRouteComponent> for detailed usage information.
Serving the application from a subdirectory
- React Router V6
- React Router V5
- React Location
- Next.js
react-router-v6-subdirectory
If you want to serve from a subdirectory in your refine react app. You can use basename property of <BrowserRouter>.
The <RouterComponent> in the react-router-v6 package passes all its properties to the <BrowserRouter> component. Therefore, a <BrowserRouter> property that we will give to the <RouterComponent> is passed to the <BrowserRouter> that wraps our application.
In the example below you can see how to serve the application in a subdirectory.
import { Refine } from "@pankod/refine-core";
import routerProvider from "@pankod/refine-react-router-v6";
import dataProvider from "@pankod/refine-simple-rest";
import "@pankod/refine/dist/styles.min.css";
import { PostList, PostCreate, PostEdit, PostShow } from "pages/posts";
const API_URL = "https://api.fake-rest.refine.dev";
const { RouterComponent } = routerProvider;
const CustomRouterComponent = () => <RouterComponent basename="/admin" />;
const App: React.FC = () => {
return (
<Refine
routerProvider={{
...routerProvider,
RouterComponent: CustomRouterComponent,
}}
dataProvider={dataProvider(API_URL)}
resources={[
{
name: "posts",
list: PostList,
create: PostCreate,
edit: PostEdit,
show: PostShow,
},
]}
/>
);
};
export default App;
Now you can access our application at www.domain.com/admin.
react-router-v5-subdirectory
If you want to serve from a subdirectory in your refine react app. You can use basename property of <BrowserRouter>.
The <RouterComponent> in the react-router-v5 package passes all its properties to the <BrowserRouter> component. Therefore, a <BrowserRouter> property that we will give to the <RouterComponent> is passed to the <BrowserRouter> that wraps our application.
In the example below you can see how to serve the application in a subdirectory.
import { Refine } from "@pankod/refine-core";
import routerProvider from "@pankod/refine-react-router-v6";
import dataProvider from "@pankod/refine-simple-rest";
import "@pankod/refine/dist/styles.min.css";
import { PostList, PostCreate, PostEdit, PostShow } from "pages/posts";
const API_URL = "https://api.fake-rest.refine.dev";
const { RouterComponent } = routerProvider;
const CustomRouterComponent = () => <RouterComponent basename="/admin" />;
const App: React.FC = () => {
return (
<Refine
routerProvider={{
...routerProvider,
RouterComponent: CustomRouterComponent,
}}
dataProvider={dataProvider(API_URL)}
resources={[
{
name: "posts",
list: PostList,
create: PostCreate,
edit: PostEdit,
show: PostShow,
},
]}
/>
);
};
export default App;
Now you can access our application at www.domain.com/admin.
react-location-subdirectory
If you want to serve from a subdirectory in your refine react app. You can use basepath property of <Router>.
The <RouterComponent> in the react-location package passes all its properties to the <Router> component. Therefore, a <Router> property that we will give to the <RouterComponent> is passed to the <Router> that wraps our application.
CAUTION
Using basepath in react-location causes some problems and as the development of react-location is discontinued it's unlikely to be fixed. Please see here for more information and a workaround.
In the example below you can see how to serve the application in a subdirectory.
import { Refine } from "@pankod/refine-core";
import routerProvider from "@pankod/refine-react-location";
import dataProvider from "@pankod/refine-simple-rest";
import "@pankod/refine/dist/styles.min.css";
import { PostList, PostCreate, PostEdit, PostShow } from "pages/posts";
const API_URL = "https://api.fake-rest.refine.dev";
const { RouterComponent, location } = routerProvider;
const CustomRouterComponent = () => (
<RouterComponent location={location} basepath="/admin" />
);
const App: React.FC = () => {
return (
<Refine
routerProvider={{
...routerProvider,
RouterComponent: CustomRouterComponent,
}}
dataProvider={dataProvider(API_URL)}
resources={[
{
name: "posts",
list: PostList,
create: PostCreate,
edit: PostEdit,
show: PostShow,
},
]}
/>
);
};
export default App;
Now you can access our application at www.domain.com/admin.
nextjs-subdirectory
To serve your application from a subdirectory in your refine Nextjs application, simply add basePath to your next.config.js file.
module.exports = {
basePath: "/admin",
};
Refer to the Nextjs documentation for detailed usage information. →
Now you can access our application at www.domain.com/admin.
Changing the initial route of your application
refine initially shows the DashboardPage component from <Refine/> props, if there are no DashboardPage component is present, refine redirects to the first list page in the resources array. You can change this behavior by passing initialRoute value to the RouterComponents of the router providers.
- React Router V6
- React Location
- Next.js
- Remix
react-router-v6-initial-route
RouterComponent property in the routerProvider from @pankod/refine-react-router-v6 checks for the initialRoute property in its context. If it is present, it will redirect to the given route. By default routerProvider is using BrowserRouterComponent but both HashRouterComponent and MemoryRouterComponent also supports initialRoute property.
In the example below, BrowserRouterComponent is used and the initial route is set to /users.
import { Refine } from "@pankod/refine-core";
import routerProvider, {
BrowserRouterComponent,
} from "@pankod/refine-react-router-v6";
import dataProvider from "@pankod/refine-simple-rest";
import "@pankod/refine/dist/styles.min.css";
import { PostList, PostCreate, PostEdit, PostShow } from "pages/posts";
import { UserList, UserShow } from "pages/users";
const API_URL = "https://api.fake-rest.refine.dev";
const App: React.FC = () => {
return (
<Refine
routerProvider={{
...routerProvider,
RouterComponent: BrowserRouterComponent.bind({
initialRoute: "/users",
}),
}}
dataProvider={dataProvider(API_URL)}
resources={[
{
name: "posts",
list: PostList,
create: PostCreate,
edit: PostEdit,
show: PostShow,
},
{
name: "users",
list: UserList,
show: UserShow,
},
]}
/>
);
};
export default App;
react-location-initial-route
RouterComponent property in the routerProvider from @pankod/refine-location checks for the initialRoute property in its context. If it is present, it will redirect to the given route.
In the example below, you can see how the initial route is set to /users.
import { Refine } from "@pankod/refine-core";
import routerProvider from "@pankod/refine-react-location";
import dataProvider from "@pankod/refine-simple-rest";
import "@pankod/refine/dist/styles.min.css";
import { PostList, PostCreate, PostEdit, PostShow } from "pages/posts";
import { UserList, UserShow } from "pages/users";
const API_URL = "https://api.fake-rest.refine.dev";
const App: React.FC = () => {
return (
<Refine
routerProvider={{
...routerProvider,
RouterComponent: routerProvider.RouterComponent.bind({
initialRoute: "/users",
}),
}}
dataProvider={dataProvider(API_URL)}
resources={[
{
name: "posts",
list: PostList,
create: PostCreate,
edit: PostEdit,
show: PostShow,
},
{
name: "users",
list: UserList,
show: UserShow,
},
]}
/>
);
};
export default App;
nextjs-initial-route
Since Next.js uses file system based routing, instead of the routerProvider prop of <Refine/>, you should pass the initialRoute property to the context of the NextRouteComponent from @pankod/refine-nextjs-router.
In the example below, the initial route is set to /users.
import { NextRouteComponent } from "@pankod/refine-nextjs-router";
export default NextRouteComponent.bind({ initialRoute: "/users" });
INFORMATION
There is also a way to redirect to a custom page by using file system based routing. If you want to take the advantage of the file system based routing, you can create an index.tsx file in the pages directory and redirect to the route you want.
remix-initial-route
Since Remix uses file system based routing, instead of the routerProvider prop of <Refine/>, you should pass the initialRoute property to the context of the RemixRouteComponent from @pankod/refine-remix-router.
In the example below, the initial route is set to /users.
import { RemixRouteComponent } from "@pankod/refine-remix-router";
export default RemixRouteComponent.bind({ initialRoute: "/users" });
TIP
Splat routes are the recommended way to handle refine routing in Remix apps. All you need to do is to create a $.tsx file in the app/routes directory and export the RemixRouteComponent in it.
INFORMATION
Splat routes in Remix, does not catch the index route. So if you want to redirect to a custom page by using file system based routing, you should create a index.tsx file. Inside the index.tsx file, you can export the RemixRouteComponent by binding the initialRoute property or you can have a redirect in the loader function of the route by using redirect function from @remix-run/node.