From ee4d0d112a7742fc799cd11ffe2eb3c5165d7bcb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ali=20Emir=20=C5=9Een?= Date: Thu, 22 Sep 2022 15:32:59 +0300 Subject: [PATCH] feat: custom index route (#2486) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore(core): fix filename * feat(nextjs): add ability to fetch catch-all refine page to useParams * chore: update `refine-next` example with catch-all route * refactor(core): update action types * fix: check `options.route` for `resource.parentName` * feat(nextjs): add ability to parse catch-all refine route * chore(refine-next-example): remove unused * chore: add changesets * feat(nextjs): add initial route support to nextjs-router * chore: add initialRoute comment to refine-next * chore: add eslint ignore for unused vars * feat(react-router-v6): add `initialRoute` for `routerComponent` * refactor: replace `React.ReactNode` with `JSX.Element` * chore: add changesets * fix(react-router-v6): non declared `this` issue * chore: upgrade to `@tanstac/react-location` * feat(react-location): add fallback route support * feat: add `initialRoute` support * chore: add changeset * fix: unused-var `children` * feat: add `handleRefineParams` helper to `refine-nextjs-router` * chore: use `handleRefineParams` helper to `refine-nextjs-router` * chore: add changeset * feat(remix): add ability to parse splat params for refine * feat(remix): add ability to change initial route for refine * chore(react-location): add comment * chore(remix): update examples with splat * chore(base): add comment * chore(examples): move react-location example to top * docs(examples): update react-location example path * chore: add changeset * chore: fix typo Co-authored-by: Ömer Faruk APLAK * fix: `handleRefineParams` return type * docs(routers): add `initialRoute` section to `router-provider` docs * docs: add splat and catch-all support to docs Co-authored-by: Ömer Faruk APLAK --- .changeset/brave-lemons-mix.md | 5 + .changeset/calm-lizards-reflect.md | 5 + .changeset/dirty-bottles-look.md | 5 + .changeset/famous-items-fetch.md | 9 + .changeset/funny-crabs-compare.md | 5 + .changeset/gorgeous-keys-relax.md | 5 + .changeset/plenty-suns-wink.md | 5 + .changeset/poor-keys-rush.md | 5 + .changeset/spicy-monkeys-suffer.md | 5 + .changeset/violet-tigers-call.md | 5 + .eslintrc | 19 +- .../docs/advanced-tutorials/ssr/nextjs.md | 197 ++++++++++-------- .../docs/advanced-tutorials/ssr/remix.md | 154 +++++++++----- .../core/providers/router-provider.md | 163 ++++++++++++++- .../router-provider/react-location.md | 4 +- .../router-provider/react-location.md | 2 +- examples/base/antd/src/App.tsx | 14 +- .../react-location/.gitignore | 0 .../react-location/package.json | 0 .../react-location/public/favicon.ico | Bin .../react-location/public/index.html | 0 .../react-location/public/manifest.json | 0 .../react-location/src/App.tsx | 14 +- .../react-location/src/index.tsx | 0 .../react-location/src/interfaces/index.d.ts | 0 .../react-location/src/pages/posts/create.tsx | 0 .../react-location/src/pages/posts/edit.tsx | 0 .../react-location/src/pages/posts/index.tsx | 0 .../react-location/src/pages/posts/list.tsx | 0 .../react-location/src/pages/posts/show.tsx | 0 .../react-location/src/react-app-env.d.ts | 0 .../react-location/tsconfig.json | 0 examples/refine-next/.babelrc | 1 - examples/refine-next/next.config.js | 7 - examples/refine-next/package.json | 1 - examples/refine-next/pages/[[...refine]].tsx | 71 +++++++ .../pages/[resource]/[action]/[id].tsx | 21 -- .../pages/[resource]/[action]/index.tsx | 21 -- .../refine-next/pages/[resource]/index.tsx | 37 ---- examples/refine-next/pages/index.tsx | 20 -- examples/remix/antd/app/root.tsx | 10 +- examples/remix/antd/app/routes/$.tsx | 62 ++++++ .../routes/$resource/$action/$id/index.tsx | 11 - .../app/routes/$resource/$action/index.tsx | 12 -- .../remix/antd/app/routes/$resource/index.tsx | 35 ---- examples/remix/antd/app/routes/index.tsx | 41 ++-- examples/remix/headless/app/routes/$.tsx | 72 +++++++ .../routes/$resource/$action/$id/index.tsx | 1 - .../app/routes/$resource/$action/index.tsx | 1 - .../headless/app/routes/$resource/index.tsx | 33 --- examples/remix/headless/app/routes/index.tsx | 35 ++-- .../components/containers/refine/index.tsx | 4 +- .../core/src/components/pages/error/index.tsx | 4 +- .../src/contexts/refine/IRefineContext.ts | 14 +- .../helpers/redirectPage/index.spec.ts | 4 +- .../definitions/helpers/redirectPage/index.ts | 12 +- .../helpers/routeGenerator/index.ts | 29 +-- packages/core/src/hooks/form/useForm.ts | 9 +- packages/core/src/hooks/redirection/index.ts | 6 +- packages/core/src/hooks/refine/index.ts | 2 +- ...useRefineContex.ts => useRefineContext.ts} | 0 packages/core/src/index.tsx | 5 + packages/core/src/interfaces/actions.ts | 16 ++ packages/core/src/interfaces/index.ts | 6 +- .../core/src/interfaces/redirectionTypes.ts | 1 - .../interfaces/resourceErrorRouterParams.ts | 4 +- .../src/interfaces/resourceRouterParams.ts | 4 +- packages/nextjs-router/src/index.ts | 2 + .../nextjs-router/src/nextRouteComponent.tsx | 29 ++- packages/nextjs-router/src/routerProvider.ts | 10 +- packages/nextjs-router/src/useParams.ts | 62 ++++++ packages/react-location/package.json | 8 +- packages/react-location/src/index.ts | 2 +- packages/react-location/src/prompt.tsx | 2 +- .../react-location/src/routerComponent.tsx | 43 ++-- .../react-router-v6/src/routeProvider.tsx | 16 +- .../react-router-v6/src/routerComponent.tsx | 49 +++-- packages/remix/src/index.ts | 1 + packages/remix/src/routeComponent.tsx | 32 ++- packages/remix/src/routerProvider.ts | 10 +- packages/remix/src/useParams.ts | 65 ++++++ 81 files changed, 1060 insertions(+), 504 deletions(-) create mode 100644 .changeset/brave-lemons-mix.md create mode 100644 .changeset/calm-lizards-reflect.md create mode 100644 .changeset/dirty-bottles-look.md create mode 100644 .changeset/famous-items-fetch.md create mode 100644 .changeset/funny-crabs-compare.md create mode 100644 .changeset/gorgeous-keys-relax.md create mode 100644 .changeset/plenty-suns-wink.md create mode 100644 .changeset/poor-keys-rush.md create mode 100644 .changeset/spicy-monkeys-suffer.md create mode 100644 .changeset/violet-tigers-call.md rename examples/{routerProvider => }/react-location/.gitignore (100%) rename examples/{routerProvider => }/react-location/package.json (100%) rename examples/{routerProvider => }/react-location/public/favicon.ico (100%) rename examples/{routerProvider => }/react-location/public/index.html (100%) rename examples/{routerProvider => }/react-location/public/manifest.json (100%) rename examples/{routerProvider => }/react-location/src/App.tsx (66%) rename examples/{routerProvider => }/react-location/src/index.tsx (100%) rename examples/{routerProvider => }/react-location/src/interfaces/index.d.ts (100%) rename examples/{routerProvider => }/react-location/src/pages/posts/create.tsx (100%) rename examples/{routerProvider => }/react-location/src/pages/posts/edit.tsx (100%) rename examples/{routerProvider => }/react-location/src/pages/posts/index.tsx (100%) rename examples/{routerProvider => }/react-location/src/pages/posts/list.tsx (100%) rename examples/{routerProvider => }/react-location/src/pages/posts/show.tsx (100%) rename examples/{routerProvider => }/react-location/src/react-app-env.d.ts (100%) rename examples/{routerProvider => }/react-location/tsconfig.json (100%) delete mode 100644 examples/refine-next/.babelrc delete mode 100644 examples/refine-next/next.config.js create mode 100644 examples/refine-next/pages/[[...refine]].tsx delete mode 100644 examples/refine-next/pages/[resource]/[action]/[id].tsx delete mode 100644 examples/refine-next/pages/[resource]/[action]/index.tsx delete mode 100644 examples/refine-next/pages/[resource]/index.tsx delete mode 100644 examples/refine-next/pages/index.tsx create mode 100644 examples/remix/antd/app/routes/$.tsx delete mode 100644 examples/remix/antd/app/routes/$resource/$action/$id/index.tsx delete mode 100644 examples/remix/antd/app/routes/$resource/$action/index.tsx delete mode 100644 examples/remix/antd/app/routes/$resource/index.tsx create mode 100644 examples/remix/headless/app/routes/$.tsx delete mode 100644 examples/remix/headless/app/routes/$resource/$action/$id/index.tsx delete mode 100644 examples/remix/headless/app/routes/$resource/$action/index.tsx delete mode 100644 examples/remix/headless/app/routes/$resource/index.tsx rename packages/core/src/hooks/refine/{useRefineContex.ts => useRefineContext.ts} (100%) create mode 100644 packages/core/src/interfaces/actions.ts delete mode 100644 packages/core/src/interfaces/redirectionTypes.ts create mode 100644 packages/nextjs-router/src/useParams.ts create mode 100644 packages/remix/src/useParams.ts diff --git a/.changeset/brave-lemons-mix.md b/.changeset/brave-lemons-mix.md new file mode 100644 index 000000000000..188e154f7bd9 --- /dev/null +++ b/.changeset/brave-lemons-mix.md @@ -0,0 +1,5 @@ +--- +"@pankod/refine-core": patch +--- + +Fixed the issue in resource routes not taking `options.route` of parent resource into account. diff --git a/.changeset/calm-lizards-reflect.md b/.changeset/calm-lizards-reflect.md new file mode 100644 index 000000000000..8847ef6188ca --- /dev/null +++ b/.changeset/calm-lizards-reflect.md @@ -0,0 +1,5 @@ +--- +"@pankod/refine-react-router-v6": minor +--- + +Added ability to manage the initial route of **refine** by binding `initialRoute` variable to `RouterComponent` component. diff --git a/.changeset/dirty-bottles-look.md b/.changeset/dirty-bottles-look.md new file mode 100644 index 000000000000..c665529896c6 --- /dev/null +++ b/.changeset/dirty-bottles-look.md @@ -0,0 +1,5 @@ +--- +"@pankod/refine-nextjs-router": minor +--- + +Added `handleRefineParams` helper function to handle catch-all refine params. diff --git a/.changeset/famous-items-fetch.md b/.changeset/famous-items-fetch.md new file mode 100644 index 000000000000..14d438e7e4e3 --- /dev/null +++ b/.changeset/famous-items-fetch.md @@ -0,0 +1,9 @@ +--- +"@pankod/refine-core": minor +--- + +Combine action related types into a single file and derive types from it to avoid future inconsistencies. + +Renamed `RedirectionTypes` type to `RedirectAction`. + +Updated every type definition of actions to use the new `Action` type or derivations of it. diff --git a/.changeset/funny-crabs-compare.md b/.changeset/funny-crabs-compare.md new file mode 100644 index 000000000000..edd8fa61af44 --- /dev/null +++ b/.changeset/funny-crabs-compare.md @@ -0,0 +1,5 @@ +--- +"@pankod/refine-remix-router": minor +--- + +Added ability to manage the initial route of **refine** by binding `initialRoute` variable to `RemixRouteComponent` component. diff --git a/.changeset/gorgeous-keys-relax.md b/.changeset/gorgeous-keys-relax.md new file mode 100644 index 000000000000..4840a989a2f0 --- /dev/null +++ b/.changeset/gorgeous-keys-relax.md @@ -0,0 +1,5 @@ +--- +"@pankod/refine-remix-router": minor +--- + +Add splat route support to remix with `handleRefineParams` helper. diff --git a/.changeset/plenty-suns-wink.md b/.changeset/plenty-suns-wink.md new file mode 100644 index 000000000000..f80b69c5d8d2 --- /dev/null +++ b/.changeset/plenty-suns-wink.md @@ -0,0 +1,5 @@ +--- +"@pankod/refine-nextjs-router": minor +--- + +Added ability to parse catch-all refine route in Next.js router. This way, instead of creating multiple pages, users can only create one page at the root `[[...refine]].tsx` and handle all params for the app. diff --git a/.changeset/poor-keys-rush.md b/.changeset/poor-keys-rush.md new file mode 100644 index 000000000000..7d3c01e5243e --- /dev/null +++ b/.changeset/poor-keys-rush.md @@ -0,0 +1,5 @@ +--- +"@pankod/refine-react-location": minor +--- + +Added ability to manage the initial route of **refine** by binding `initialRoute` variable to `RouterComponent` component. diff --git a/.changeset/spicy-monkeys-suffer.md b/.changeset/spicy-monkeys-suffer.md new file mode 100644 index 000000000000..9df17050083b --- /dev/null +++ b/.changeset/spicy-monkeys-suffer.md @@ -0,0 +1,5 @@ +--- +"@pankod/refine-nextjs-router": minor +--- + +Added ability to manage the initial route of **refine** by binding `initialRoute` variable to `NextRouteComponent` component. diff --git a/.changeset/violet-tigers-call.md b/.changeset/violet-tigers-call.md new file mode 100644 index 000000000000..932d2a61a6c2 --- /dev/null +++ b/.changeset/violet-tigers-call.md @@ -0,0 +1,5 @@ +--- +"@pankod/refine-react-location": minor +--- + +Added support for fallback/404 with `catchAll` and `ErrorComponent`. diff --git a/.eslintrc b/.eslintrc index 8da2db01928c..522a3ffce5bf 100644 --- a/.eslintrc +++ b/.eslintrc @@ -8,19 +8,14 @@ }, "root": true, "parser": "@typescript-eslint/parser", - "plugins": [ - "prettier", - "@typescript-eslint" - ], + "plugins": ["prettier", "@typescript-eslint"], "extends": [ "plugin:react/recommended", "plugin:@typescript-eslint/recommended" ], "overrides": [ { - "files": [ - "*.js" - ], + "files": ["*.js"], "rules": { "@typescript-eslint/no-var-requires": "off" } @@ -32,7 +27,15 @@ "@typescript-eslint/explicit-function-return-type": "off", "react/prop-types": "off", "react/react-in-jsx-scope": "off", - "prettier/prettier": "error" + "prettier/prettier": "error", + "no-unused-vars": "off", + "@typescript-eslint/no-unused-vars": [ + "warn", + { + "argsIgnorePattern": "^_", + "varsIgnorePattern": "^_" + } + ] }, "settings": { "react": { diff --git a/documentation/docs/advanced-tutorials/ssr/nextjs.md b/documentation/docs/advanced-tutorials/ssr/nextjs.md index dd16d35dfedd..c1938fab40a2 100644 --- a/documentation/docs/advanced-tutorials/ssr/nextjs.md +++ b/documentation/docs/advanced-tutorials/ssr/nextjs.md @@ -3,12 +3,11 @@ id: nextjs title: Next.js --- -**refine** can be used with [**Next.js**][Nextjs] to SSR your pages. It doesn't get in the way and follows Next.js conventions and also provides helper modules when necessary. - +**refine** can be used with [**Next.js**][nextjs] to SSR your pages. It doesn't get in the way and follows Next.js conventions and also provides helper modules when necessary. ## Setup -[**nextjs-router**][NextjsRouter] package provided by **refine** must be used for the [`routerProvider`][routerProvider] +[**nextjs-router**][nextjsrouter] package provided by **refine** must be used for the [`routerProvider`][routerprovider] ```bash npm i @pankod/refine-core @pankod/refine-antd @pankod/refine-nextjs-router @@ -20,6 +19,7 @@ We recommend [**superplate**][superplate] to initialize your refine projects. It ``` npx superplate-cli -o refine-nextjs my-refine-nextjs-app ``` + ::: :::caution @@ -28,17 +28,25 @@ To make this example more visual, we used the [`@pankod/refine-antd`](https://gi ## Usage -[``][refine] must wrap your pages in a [custom App][NextjsCustomApp] component. This way your [pages][NextjsPages] are integrated to refine. +[``][refine] must wrap your pages in a [custom App][nextjscustomapp] component. This way your [pages][nextjspages] are integrated to refine. ```tsx title="pages/_app.tsx" import { AppProps } from "next/app"; import { Refine } from "@pankod/refine-core"; -import { Layout, ReadyPage, notificationProvider, ErrorComponent } from "@pankod/refine-antd"; +import { + Layout, + ReadyPage, + notificationProvider, + ErrorComponent, +} from "@pankod/refine-antd"; import dataProvider from "@pankod/refine-simple-rest"; import routerProvider from "@pankod/refine-nextjs-router"; +import { PostList, PostEdit, PostCreate, PostShow } from "pages/posts"; +import { UserList, UserShow } from "pages/users"; + const API_URL = "https://api.fake-rest.refine.dev"; function MyApp({ Component, pageProps }: AppProps): JSX.Element { @@ -51,6 +59,20 @@ function MyApp({ Component, pageProps }: AppProps): JSX.Element { ReadyPage={ReadyPage} notificationProvider={notificationProvider} catchAll={} + resources={[ + { + name: "posts", + list: PostList, + create: PostCreate, + edit: PostEdit, + show: PostShow, + }, + { + name: "users", + list: UserList, + show: UserShow, + }, + ]} > @@ -67,17 +89,13 @@ Let's say we want to show a list of users in `/users`. After creating `users.tsx ```tsx title="pages/users.tsx" import { LayoutWrapper } from "@pankod/refine-core"; -import { - useTable, - List, - Table, -} from "@pankod/refine-antd"; +import { useTable, List, Table } from "@pankod/refine-antd"; const API_URL = "https://api.fake-rest.refine.dev"; // highlight-start export const UserList: React.FC = () => { const { tableProps } = useTable({ - resource: "users" + resource: "users", }); return ( @@ -101,7 +119,7 @@ export default UserList; ``` :::important -Notice how we passed `resource` prop to [`useTable`][useTable]. This is necessary since for `useTable` to be able to get `resource` name from route, it needs to be a route parameter in a dynamic route. [Refer here](#standard-crud-page) where standard CRUD pages can be built with dynamic routing. +Notice how we passed `resource` prop to [`useTable`][usetable]. This is necessary since for `useTable` to be able to get `resource` name from route, it needs to be a route parameter in a dynamic route. [Refer here](#standard-crud-page) where standard CRUD pages can be built with dynamic routing. ::: :::important @@ -110,7 +128,7 @@ We also used `` to show the page in the layout provided to [` }> = ({ - users + users, }) => { -// highlight-end + // highlight-end const { tableProps } = useTable({ resource: "users", -// highlight-start + // highlight-start queryOptions: { initialData: users, }, -// highlight-end + // highlight-end }); return ( @@ -174,7 +188,7 @@ interface IPost { export default UserList; ``` -We use the [`getList`][getList] method from our [`dataProvider`][dataProvider] to fetch `users` data and pass through `props` as conventionally done in Next.js. Then `users` data is available in the props of our `/users` page. [`useTable`][useTable] can take options for underlying react-query queries with `queryOptions`. Passing `users` data to its `initialData` loads the data on server side. +We use the [`getList`][getlist] method from our [`dataProvider`][dataprovider] to fetch `users` data and pass through `props` as conventionally done in Next.js. Then `users` data is available in the props of our `/users` page. [`useTable`][usetable] can take options for underlying react-query queries with `queryOptions`. Passing `users` data to its `initialData` loads the data on server side. :::tip We used `getList` from `dataProvider` but data can be fetched in any way you desire. @@ -182,31 +196,30 @@ We used `getList` from `dataProvider` but data can be fetched in any way you des ## Standard CRUD Page -**nextjs-router** package provides `NextRouteComponent` for pages with the dynamic route `/[resource]/[action]/[id]` and root `/`. Simply export the component from the page and add a [data fetching function][dataFetching] +**nextjs-router** package provides `NextRouteComponent` for routing in **refine** resources. Simply export the component from the page and add a [data fetching function][datafetching]. While you can create pages with defined params like `[resource]/[action]/[id].tsx`, we recommend using a catch-all route to handle all **refine** routing in a single file. You can start by creating a `[[...refine]].tsx` file under `pages` in your Nextjs app: -```tsx title="pages/[resource]/index.tsx" +```tsx title="pages/[[...refine]].tsx" export { NextRouteComponent as default } from "@pankod/refine-nextjs-router"; - -export const getServerSideProps: GetServerSideProps = async () => { - return { props: {} }; -}; ``` -:::warning -`NextRouteComponent` doesn't support [automatic static optimization][autoStaticOpt] currently, since it requires route parameters thus a data fetching function must be defined. -::: +:::info + +You can also define routes without using `[[...refine]].tsx` file like below, but a catch-all route is an easier approach with nested route support. + +Export `NextRouteComponent` as default in the following pages: -`NextRouteComponent` can be used in the following pages: -- `pages/[resource].tsx` -- `pages/[resource]/[action].tsx` -- `pages/[resource]/[action]/[id].tsx` -- `pages/index.tsx` +- `pages/[resource].tsx` +- `pages/[resource]/[action].tsx` +- `pages/[resource]/[action]/[id].tsx` +- `pages/index.tsx` `NextRouteComponent` will use route parameters `resource` and `action` and render the associated component defined in [`resources`][refine]. -- `list` component will be rendered for `/[resource]` route -- `create`, `edit` and `show` will be rendered for `/[resource]/[action]` and `/[resource]/[action]/[id]` routes -- For the root `/` route, it will render `DashboardPage` if it's defined and if not will navigate to the first resource in `resources`. +- `list` component will be rendered for `/[resource]` route +- `create`, `edit` and `show` will be rendered for `/[resource]/[action]` and `/[resource]/[action]/[id]` routes +- For the root `/` route, it will render `DashboardPage` if it's defined and if not will navigate to the first resource in `resources`. + +::: :::important `NextRouteComponent` will wrap the page with `Layout` provided to [``][refine] @@ -221,12 +234,14 @@ type NextRouteComponentProps = { initialData?: any; }; ``` + `initialData` must be passed as props from `getServerSideProps`. `NextRouteComponent` will pass this data as `initialData` to the `list`, `create`, `edit` and `show` components. For example, for a `list` component that will be rendered for `/[resource]`, the page can use SSR like this: -```tsx title="pages/[resource]/index.tsx" +```tsx title="pages/[[...refine]].tsx" export { NextRouteComponent as default } from "@pankod/refine-nextjs-router"; +import { handleRefineParams } from "@pankod/refine-nextjs-router"; import dataProvider from "@pankod/refine-simple-rest"; import { GetServerSideProps } from "next"; @@ -234,39 +249,50 @@ import { GetServerSideProps } from "next"; const API_URL = "https://api.fake-rest.refine.dev"; export const getServerSideProps: GetServerSideProps = async (context) => { - - const { query } = context; - - try { - const data = await dataProvider(API_URL).getList({ - resource: query["resource"] as string, - }); - - return { - props: { - initialData: data, - }, - }; + const { resource, action, id } = handleRefineParams(context.params?.refine); + + try { + if (resource && action === "show" && id) { + const data = await dataProvider(API_URL).getOne({ + // we're slicing the resource param to get the resource name from the last part + resource: resource.slice(resource.lastIndexOf("/") + 1), + id, + }); + + return { + props: { + initialData: data, + }, + }; + } else if (resource && !action && !id) { + const data = await dataProvider(API_URL).getList({ + // we're slicing the resource param to get the resource name from the last part + resource: resource.slice(resource.lastIndexOf("/") + 1), + }); + + return { + props: { + initialData: data, + }, + }; + } } catch (error) { return { props: {} }; } -}; + return { props: {} }; +}; ``` And in the `list` component for a `resource` e.g. "posts": ```tsx title="src/components/posts/list.tsx" import { GetListResponse, IResourceComponentsProps } from "@pankod/refine-core"; -import { - useTable, - List, - Table, -} from "@pankod/refine-antd"; +import { useTable, List, Table } from "@pankod/refine-antd"; export const PostList: React.FC< IResourceComponentsProps> -// highlight-next-line + // highlight-next-line > = ({ initialData }) => { const { tableProps } = useTable({ // highlight-start @@ -292,23 +318,28 @@ interface IPost { } ``` +:::tip + +You can also achieve SSR with `getStaticProps` and `getStaticPaths` for static generation. All you need to do is to add the paths you want to statically generate to `getStaticPaths` and pass the data as `initialData` from `getStaticProps`. + +::: + ## Server Side Authentication **nextjs-router** package provides `checkAuthentication` to easily handle server side authentication. -```tsx title="pages/[resource]/index.tsx" +```tsx title="pages/[[...refine]].tsx" export { NextRouteComponent as default } from "@pankod/refine-nextjs-router"; // highlight-next-line import { checkAuthentication } from "@pankod/refine-nextjs-router"; - + import { GetServerSideProps } from "next"; -import {authProvider} from "../../src/authProvider"; - +import { authProvider } from "../../src/authProvider"; + const API_URL = "https://api.fake-rest.refine.dev"; - + export const getServerSideProps: GetServerSideProps = async (context) => { - // highlight-start const { isAuthenticated, ...props } = await checkAuthentication( authProvider, @@ -319,7 +350,7 @@ export const getServerSideProps: GetServerSideProps = async (context) => { return props; } // highlight-end - + return { props: {}, }; @@ -341,7 +372,6 @@ import dataProvider from "@pankod/refine-simple-rest"; const API_URL = "https://api.fake-rest.refine.dev"; export const getServerSideProps: GetServerSideProps = async (context) => { - // highlight-start const { parsedCurrent, parsedPageSize, parsedSorter, parsedFilters } = parseTableParamsFromQuery(context.query); @@ -362,7 +392,6 @@ export const getServerSideProps: GetServerSideProps = async (context) => { props: { users: data }, }; }; - ``` `parseTableParams` parses the query string and returns query parameters([refer here for their interfaces][interfaces]). They can be directly used for `dataProvider` methods that accepts them. @@ -374,19 +403,19 @@ export const getServerSideProps: GetServerSideProps = async (context) => { title="refine-next" > -[Nextjs]: https://nextjs.org/docs/getting-started -[NextjsRouter]: https://www.npmjs.com/package/@pankod/refine-nextjs-router -[routerProvider]: /api-reference/core/providers/router-provider.md +[nextjs]: https://nextjs.org/docs/getting-started +[nextjsrouter]: https://www.npmjs.com/package/@pankod/refine-nextjs-router +[routerprovider]: /api-reference/core/providers/router-provider.md [superplate]: https://github.com/pankod/superplate -[NextjsCustomApp]: https://nextjs.org/docs/advanced-features/custom-app +[nextjscustomapp]: https://nextjs.org/docs/advanced-features/custom-app [refine]: /api-reference/core/components/refine-config.md -[NextjsPages]: https://nextjs.org/docs/basic-features/pages -[useTable]: /api-reference/core/hooks/useTable.md -[ReactQuerySSR]: https://react-query.tanstack.com/guides/ssr#using-initialdata -[ReactQuery]: https://react-query.tanstack.com/ -[getList]: /api-reference/core/providers/data-provider.md#getlist -[dataProvider]: /api-reference/core/providers/data-provider.md -[useTable]: /api-reference/core/hooks/useTable.md +[nextjspages]: https://nextjs.org/docs/basic-features/pages +[usetable]: /api-reference/core/hooks/useTable.md +[reactqueryssr]: https://react-query.tanstack.com/guides/ssr#using-initialdata +[reactquery]: https://react-query.tanstack.com/ +[getlist]: /api-reference/core/providers/data-provider.md#getlist +[dataprovider]: /api-reference/core/providers/data-provider.md +[usetable]: /api-reference/core/hooks/useTable.md [interfaces]: /api-reference/core/interfaces.md/#crudfilters -[autoStaticOpt]: https://nextjs.org/docs/advanced-features/automatic-static-optimization -[dataFetching]: https://nextjs.org/docs/basic-features/data-fetching +[autostaticopt]: https://nextjs.org/docs/advanced-features/automatic-static-optimization +[datafetching]: https://nextjs.org/docs/basic-features/data-fetching diff --git a/documentation/docs/advanced-tutorials/ssr/remix.md b/documentation/docs/advanced-tutorials/ssr/remix.md index a7b24fc04536..cd90a48e84b7 100644 --- a/documentation/docs/advanced-tutorials/ssr/remix.md +++ b/documentation/docs/advanced-tutorials/ssr/remix.md @@ -3,12 +3,10 @@ id: remix title: Remix --- -**refine** can be used with [**Remix**][Remix] to SSR your pages. It doesn't get in the way and follows Remix conventions and also provides helper modules when necessary. - +**refine** can be used with [**Remix**][remix] to SSR your pages. It doesn't get in the way and follows Remix conventions and also provides helper modules when necessary. ## Setup - ```bash npm i @pankod/refine-core @pankod/refine-remix-router @pankod/refine-simple-rest ``` @@ -19,12 +17,12 @@ We recommend [**superplate**][superplate] to initialize your refine projects. It ```sh npx superplate-cli -o refine-remix my-refine-remix-app ``` -::: +::: ## Usage -`` should be wrapped in your `` component located in `app/root.tsx`. This way your [routes][RemixRoutes] are integrated to **refine**. +`` should be wrapped in your `` component located in `app/root.tsx`. This way your [routes][remixroutes] are integrated to **refine**. ```tsx title="app/root.tsx" import type { MetaFunction } from "@remix-run/node"; @@ -85,7 +83,7 @@ import { LayoutWrapper, useTable } from "@pankod/refine-core"; export const PostList: React.FC = () => { const { tableQueryResult } = useTable({ - resource: "posts" + resource: "posts", }); return ( @@ -122,7 +120,7 @@ export default PostList; ``` :::important -Notice how we passed `resource` prop to [`useTable`][useTable]. This is necessary since for `useTable` to be able to get `resource` name from route, it needs to be a route parameter in a dynamic route. [Refer here](#standard-crud-page) where standard CRUD pages can be built with dynamic routing. +Notice how we passed `resource` prop to [`useTable`][usetable]. This is necessary since for `useTable` to be able to get `resource` name from route, it needs to be a route parameter in a dynamic route. [Refer here](#standard-crud-page) where standard CRUD pages can be built with dynamic routing. ::: :::important @@ -131,7 +129,7 @@ We also used `` to show the page in the layout provided to [``][refine] @@ -243,13 +254,15 @@ type RemixRouteComponentProps = { initialData?: any; }; ``` + `initialData` must be passed as props from `loader`. `RemixRouteComponent` will pass this data as `initialData` to the `list`, `create`, `edit` and `show` components. -For example, for a `list` component that will be rendered for `/$resource/index.tsx`, the page can use SSR like this: +For example, for a `list` component that will be rendered for `/$.tsx`, the page can use SSR like this: -```tsx title="pages/$resource/index.tsx" +```tsx title="app/routes/$.tsx" import { json, LoaderFunction } from "@remix-run/node"; import dataProvider from "@pankod/refine-simple-rest"; +import { handleRefineParams } from "@pankod/refine-remix-router"; export { RemixRouteComponent as default } from "@pankod/refine-remix-router"; @@ -257,12 +270,37 @@ const API_URL = "https://api.fake-rest.refine.dev"; export const loader: LoaderFunction = async ({ params, request }) => { const { resource } = params; + const refineSplatParams = handleRefineParams(params["*"]); + + const { + resource = undefined, + action = undefined, + id = undefined, + } = { ...refineSplatParams, ...params }; + try { - const data = await dataProvider(API_URL).getList({ - resource: resource as string, - }); + if (resource && action === "show" && id) { + const data = await dataProvider(API_URL).getOne({ + // we're slicing the resource param to get the resource name from the last part + resource: `${resource}`.slice( + `${resource}`.lastIndexOf("/") + 1, + ), + id, + }); + + return json({ initialData: data }); + } else if (resource && !action && !id) { + const data = await dataProvider(API_URL).getList({ + // we're slicing the resource param to get the resource name from the last part + resource: `${resource}`.slice( + `${resource}`.lastIndexOf("/") + 1, + ), + }); + + return json({ initialData: data }); + } - return json({ initialData: data }); + return null; } catch (error) { return json({}); } @@ -274,7 +312,11 @@ And in the `list` component for a `resource` e.g. "posts": ```tsx title="app/pages/posts/list.tsx" // highlight-next-line import { useLoaderData } from "@remix-run/react"; -import { useTable, GetListResponse, IResourceComponentsProps } from "@pankod/refine-core"; +import { + useTable, + GetListResponse, + IResourceComponentsProps, +} from "@pankod/refine-core"; export const PostList: React.FC< IResourceComponentsProps> @@ -356,10 +398,12 @@ export default function App() { @@ -380,7 +424,8 @@ There are two ways to do Server Side Authentication with Remix. You can choose o 2. Self service cookies! You manage authentication cookies yourself. The plus of this method is that the Authentication information can also be used on the Client Side. (recommended) ### createCookieSessionStorage -First, let's create our `AuthProvider`. For more information on `AuthProvider`, visit our [AuthProvider documentation][AuthProvider]. + +First, let's create our `AuthProvider`. For more information on `AuthProvider`, visit our [AuthProvider documentation][authprovider]. ```tsx title="app/authProvider.ts" import { AuthProvider } from "@pankod/refine-core"; @@ -435,7 +480,8 @@ export const authProvider: AuthProvider = { }, }; ``` -Next, let's create the `app/session.server.ts` file as mentioned in the [`Jokes App`][JokesApp] tutorial + +Next, let's create the `app/session.server.ts` file as mentioned in the [`Jokes App`][jokesapp] tutorial ```tsx title="app/session.server.ts" import { createCookieSessionStorage, redirect } from "@remix-run/node"; @@ -628,8 +674,7 @@ export const loader: LoaderFunction = async ({ params, request, context }) => { await requireUserId(request); return json({}); -} - +}; ``` Finally, let's make sure our users can log out. For this, we create a routes for `/logout`. @@ -731,7 +776,6 @@ export const authProvider: AuthProvider = { return Promise.resolve(); }, }; - ``` Tadaa! that's all! @@ -752,11 +796,12 @@ export const loader: LoaderFunction = async ({ params, request, context }) => { return null; }; ``` + You can also add the authentication check to the routes below -- `app/routes/$resource/index.tsx` -- `app/routes/$resource/$action/index.tsx` -- `app/routes/$resource/$action/$id/index.tsx` +- `app/routes/$resource/index.tsx` +- `app/routes/$resource/$action/index.tsx` +- `app/routes/$resource/$action/$id/index.tsx` ## `syncWithLocation` and Query Parameters in SSR @@ -795,31 +840,28 @@ export const loader: LoaderFunction = async ({ params, request }) => { return json({}); } }; - ``` `parseTableParams` parses the query string and returns query parameters([refer here for their interfaces][interfaces]). They can be directly used for `dataProvider` methods that accepts them. - ## Examples -- [Ant Design](https://ant.design/) CRUD app example ([source code](https://github.com/pankod/refine/tree/next/examples/remix/antd)) -- Headless CRUD app example ([source code](https://github.com/pankod/refine/tree/next/examples/remix/headless)) - +- [Ant Design](https://ant.design/) CRUD app example ([source code](https://github.com/pankod/refine/tree/next/examples/remix/antd)) +- Headless CRUD app example ([source code](https://github.com/pankod/refine/tree/next/examples/remix/headless)) -[Remix]: https://remix.run/ -[RemixRouter]: https://www.npmjs.com/package/@pankod/remix-router -[routerProvider]: /api-reference/core/providers/router-provider.md +[remix]: https://remix.run/ +[remixrouter]: https://www.npmjs.com/package/@pankod/remix-router +[routerprovider]: /api-reference/core/providers/router-provider.md [superplate]: https://github.com/pankod/superplate [refine]: /api-reference/core/components/refine-config.md -[RemixRoutes]: https://remix.run/docs/en/v1/api/conventions#routes -[useTable]: /api-reference/core/hooks/useTable.md -[ReactQuerySSR]: https://react-query.tanstack.com/guides/ssr#using-initialdata -[ReactQuery]: https://react-query.tanstack.com/ -[getList]: /api-reference/core/providers/data-provider.md#getlist -[dataProvider]: /api-reference/core/providers/data-provider.md -[useTable]: /api-reference/core/hooks/useTable.md +[remixroutes]: https://remix.run/docs/en/v1/api/conventions#routes +[usetable]: /api-reference/core/hooks/useTable.md +[reactqueryssr]: https://react-query.tanstack.com/guides/ssr#using-initialdata +[reactquery]: https://react-query.tanstack.com/ +[getlist]: /api-reference/core/providers/data-provider.md#getlist +[dataprovider]: /api-reference/core/providers/data-provider.md +[usetable]: /api-reference/core/hooks/useTable.md [interfaces]: /api-reference/core/interfaces.md/#crudfilters -[loader function]: https://remix.run/docs/en/v1/api/conventions#loader -[JokesApp]: https://remix.run/docs/en/v1/tutorials/jokes#authentication -[AuthProvider]: /api-reference/core/providers/auth-provider.md \ No newline at end of file +[loaderfunction]: https://remix.run/docs/en/v1/api/conventions#loader +[jokesapp]: https://remix.run/docs/en/v1/tutorials/jokes#authentication +[authprovider]: /api-reference/core/providers/auth-provider.md diff --git a/documentation/docs/api-reference/core/providers/router-provider.md b/documentation/docs/api-reference/core/providers/router-provider.md index e66396a6d108..a21fc4db4aad 100644 --- a/documentation/docs/api-reference/core/providers/router-provider.md +++ b/documentation/docs/api-reference/core/providers/router-provider.md @@ -143,9 +143,7 @@ export default function App() { - + @@ -1141,6 +1139,165 @@ 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 `` 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 `RouterComponent`s of the router providers. + + + + +`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`. + +```tsx title="src/App.tsx" +import { Refine } from "@pankod/refine-core"; +// highlight-start +import routerProvider, { + BrowserRouterComponent, +} from "@pankod/refine-react-router-v6"; +// highlight-end +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 ( + + ); +}; + +export default App; +``` + + + + +`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`. + +```tsx title="src/App.tsx" +import { Refine } from "@pankod/refine-core"; +// highlight-next-line +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 ( + + ); +}; + +export default App; +``` + + + + +Since Next.js uses file system based routing, instead of the `routerProvider` prop of ``, 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`. + +```tsx title="pages/[[...refine]].tsx" +import { NextRouteComponent } from "@pankod/refine-nextjs-router"; + +export default NextRouteComponent.bind({ initialRoute: "/users" }); +``` + +:::info +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. +::: + + + + +Since Remix uses file system based routing, instead of the `routerProvider` prop of ``, 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`. + +```tsx title="app/routes/index.tsx" +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. +::: + +:::info +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`. +::: + + + + [browserrouter]: https://github.com/pankod/refine/blob/master/packages/react-router-v6/src/routerComponent.tsx [router]: https://react-location.tanstack.com/docs/api#router [routercomponent-v6]: https://github.com/pankod/refine/blob/master/packages/react-router-v6/src/routerComponent.tsx diff --git a/documentation/docs/examples/router-provider/react-location.md b/documentation/docs/examples/router-provider/react-location.md index fc2ea937ed32..0101b8077c31 100644 --- a/documentation/docs/examples/router-provider/react-location.md +++ b/documentation/docs/examples/router-provider/react-location.md @@ -7,9 +7,9 @@ title: React Location [Refer to the refine Router Provider documentation for more information. →](/docs/api-reference/core/providers/router-provider/) -[View React Location Example Source](https://github.com/pankod/refine/tree/master/examples/routerProvider/react-location) +[View React Location Example Source](https://github.com/pankod/refine/tree/master/examples/react-location) - diff --git a/documentation/versioned_docs/version-2.xx.xx/examples/router-provider/react-location.md b/documentation/versioned_docs/version-2.xx.xx/examples/router-provider/react-location.md index 9a06dc0858f9..97461cab4140 100644 --- a/documentation/versioned_docs/version-2.xx.xx/examples/router-provider/react-location.md +++ b/documentation/versioned_docs/version-2.xx.xx/examples/router-provider/react-location.md @@ -3,7 +3,7 @@ id: react-location title: React Location --- -[View Source](https://github.com/pankod/refine/tree/master/examples/routerProvider/react-location) +[View Source](https://github.com/pankod/refine/tree/master/examples/react-location)