From 185493f07b7cf1f2ff52b39aeeebcc4c8e417e94 Mon Sep 17 00:00:00 2001 From: Gildas Garcia <1122076+djhi@users.noreply.github.com> Date: Mon, 21 Aug 2023 15:25:15 +0200 Subject: [PATCH 1/2] Convert Admin documentation to TS --- docs/Admin.md | 172 +++++++++++++++++++++++++++----------------------- 1 file changed, 92 insertions(+), 80 deletions(-) diff --git a/docs/Admin.md b/docs/Admin.md index 797e5f5dd73..ef88b4f7471 100644 --- a/docs/Admin.md +++ b/docs/Admin.md @@ -15,7 +15,7 @@ The `` component is the root component of a react-admin app. It allows to `` requires only a `dataProvider` prop, and at least one child `` to work. Here is the most basic example: -```jsx +```tsx // in src/App.js import { Admin, Resource } from 'react-admin'; import simpleRestProvider from 'ra-data-simple-rest'; @@ -36,7 +36,7 @@ export default App; In most apps, you need to pass more props to ``. Here is a more complete example taken from [the e-commerce demo](https://marmelab.com/react-admin-demo/): {% raw %} -```jsx +```tsx // in src/App.js import { Admin, Resource, CustomRoutes } from 'react-admin'; import { Route } from "react-router-dom"; @@ -82,7 +82,7 @@ const App = () => ( To make the main app component more concise, a good practice is to move the resources props to separate files. For instance, the previous example can be rewritten as: -```jsx +```tsx // in src/App.js import { Admin, Resource, CustomRoutes } from 'react-admin'; import { Route } from "react-router-dom"; @@ -166,7 +166,7 @@ Here are all the props accepted by the component: In many cases, you won't have to write a data provider, as one of the [50+ existing data providers](./DataProviderList.md) will probably fit your needs. For instance, if your API is REST-based, you can use the [Simple REST Data Provider](https://github.com/marmelab/react-admin/tree/master/packages/ra-data-simple-rest) as follows: -```jsx +```tsx // in src/App.js import simpleRestProvider from 'ra-data-simple-rest'; import { Admin, Resource } from 'react-admin'; @@ -184,17 +184,17 @@ const App = () => ( If you need to write your own, the data provider must have the following methods, all returning a promise: -```jsx +```tsx const dataProvider = { - getList: (resource, params) => Promise, - getOne: (resource, params) => Promise, - getMany: (resource, params) => Promise, - getManyReference: (resource, params) => Promise, - create: (resource, params) => Promise, - update: (resource, params) => Promise, - updateMany: (resource, params) => Promise, - delete: (resource, params) => Promise, - deleteMany: (resource, params) => Promise, + getList: (resource, params) => Promise.resolve(), + getOne: (resource, params) => Promise.resolve(), + getMany: (resource, params) => Promise.resolve(), + getManyReference: (resource, params) => Promise.resolve(), + create: (resource, params) => Promise.resolve(), + update: (resource, params) => Promise.resolve(), + updateMany: (resource, params) => Promise.resolve(), + delete: (resource, params) => Promise.resolve(), + deleteMany: (resource, params) => Promise.resolve(), } ``` @@ -209,7 +209,7 @@ The `` component expects to receive [``](./Resource.md) and [`< For instance: {% raw %} -```jsx +```tsx const App = () => ( @@ -249,7 +249,7 @@ React-admin apps contain a special route called `/auth-callback` to let external If you need a different behavior for this route, you can render a custom component by passing it as the `authCallbackPage` prop. -```jsx +```tsx import MyAuthCallbackPage from './MyAuthCallbackPage'; const App = () => ( @@ -271,7 +271,7 @@ The `authProvider` is responsible for managing authentication and permissions, u If you use a standard authentication strategy, you can use one of the [existing auth providers](./AuthProviderList.md). For instance, to use [Auth0](https://auth0.com/), you can use [`ra-auth-auth0`](https://github.com/marmelab/ra-auth-auth0): -```jsx +```tsx // in src/App.tsx import React, { useEffect, useRef, useState } from 'react'; import { Admin, Resource } from 'react-admin'; @@ -309,7 +309,7 @@ export default App; If your authentication backend isn't supported, you'll have to [write your own `authProvider`](./AuthProviderWriting.md). It's an object with 6 methods, each returning a Promise: -```jsx +```tsx const authProvider = { login: params => Promise.resolve(), logout: params => Promise.resolve(), @@ -332,7 +332,7 @@ The Auth Provider also lets you configure redirections after login/logout, anony Use this prop to make all routes and links in your Admin relative to a "base" portion of the URL pathname that they all share. This is required when using the [`BrowserHistory`](https://github.com/remix-run/history/blob/main/docs/api-reference.md#createbrowserhistory) to serve the application under a sub-path of your domain (for example https://marmelab.com/ra-enterprise-demo), or when embedding react-admin inside a single-page app with its own routing. -```jsx +```tsx import { Admin } from 'react-admin'; import { createBrowserHistory } from 'history'; @@ -354,7 +354,7 @@ When users type URLs that don't match any of the children `` component You can customize this page to use the component of your choice by passing it as the `catchAll` prop. To fit in the general design, use Material UI's `` component, and [react-admin's `` component](./Title.md): -```jsx +```tsx // in src/NotFound.js import * as React from "react"; import Card from '@mui/material/Card'; @@ -371,7 +371,7 @@ export default () => ( ); ``` -```jsx +```tsx // in src/App.js import * as React from "react"; import { Admin } from 'react-admin'; @@ -392,7 +392,7 @@ const App = () => ( By default, the homepage of an admin app is the `list` of the first child `<Resource>`. But you can also specify a custom component instead. To fit in the general design, use Material UI's `<Card>` component, and [react-admin's `<Title>` component](./Title.md) to set the title in the AppBar: -```jsx +```tsx // in src/Dashboard.js import * as React from "react"; import Card from '@mui/material/Card'; @@ -406,7 +406,7 @@ export default () => ( ); ``` -```jsx +```tsx // in src/App.js import * as React from "react"; import { Admin } from 'react-admin'; @@ -432,7 +432,7 @@ If you want to support both light and dark mode, you can provide a `darkTheme` i Your browser does not support the video tag. </video> -```jsx +```tsx import { Admin } from 'react-admin'; import { darkTheme, lightTheme } from './themes'; @@ -455,7 +455,7 @@ If you provide both a `lightTheme` and a `darkTheme`, react-admin will choose th If you prefer to always default to the light or the dark theme regardless of the user's OS preference, you can set the `defaultTheme` prop to either `light` or `dark`: -```jsx +```tsx import { Admin } from 'react-admin'; const App = () => ( @@ -480,7 +480,7 @@ The only data sent to the telemetry server is the admin domain (e.g. "example.co You can opt out of telemetry by simply adding `disableTelemetry` to the `<Admin>` component: -```jsx +```tsx // in src/App.js import * as React from "react"; import { Admin } from 'react-admin'; @@ -497,7 +497,7 @@ const App = () => ( The `i18nProvider` props let you translate the GUI. For instance, to switch the UI to French instead of the default English: -```jsx +```tsx // in src/i18nProvider.js import polyglotI18nProvider from 'ra-i18n-polyglot'; import fr from 'ra-language-french'; @@ -528,7 +528,7 @@ React-admin offers predefined layouts for you to use: - [`<Layout>`](./Layout.md): The default layout. It renders a top app bar and the navigation menu in a side bar. - [`<ContainerLayout>`](./ContainerLayout.md): A centered layout with horizontal navigation. -```jsx +```tsx import { Admin } from 'react-admin'; import { ContainerLayout } from '@react-admin/ra-navigation'; @@ -541,7 +541,7 @@ export const App = () => ( These layouts can be customized by passing props to them. For instance, you can pass a custom `appBar` prop to `<Layout>` to override the default app bar: -```jsx +```tsx // in src/MyLayout.js import { Layout } from 'react-admin'; import MyAppBar from './MyAppBar'; @@ -551,7 +551,7 @@ export const MyLayout = (props) => <Layout {...props} appBar={MyAppBar} />; Then, pass it to the `<Admin>` component as the `layout` prop: -```jsx +```tsx // in src/App.js import { Admin } from 'react-admin'; import { MyLayout } from './MyLayout'; @@ -571,7 +571,7 @@ Finally, you can also pass a custom component as the `layout` prop. It must cont If you want to customize the Login page, or switch to another authentication strategy than a username/password form, pass a component of your own as the `loginPage` prop. React-admin will display this component whenever the `/login` route is called. -```jsx +```tsx import MyLoginPage from './MyLoginPage'; const App = () => ( @@ -585,7 +585,7 @@ See The [Authentication documentation](./Authentication.md#customizing-the-login You can also disable the `/login` route completely by passing `false` to this prop. In this case, it's the `authProvider`'s responsibility to redirect unauthenticated users to a custom login page, by returning a `redirectTo` field in response to `checkAuth` (see [`authProvider.checkAuth()`](./AuthProviderWriting.md#checkauth) for details). If you fail to customize the redirection, the app will end up in an infinite loop. -```jsx +```tsx const authProvider = { // ... async checkAuth() { @@ -606,7 +606,7 @@ const App = () => ( You can override the notification component, for instance to change the notification duration. A common use case is to change the `autoHideDuration`, and force the notification to remain on screen longer than the default 4 seconds. For instance, to create a custom Notification component with a 5 seconds default: -```jsx +```tsx // in src/MyNotification.js import { Notification } from 'react-admin'; @@ -617,7 +617,7 @@ export default MyNotification; To use this custom notification component, pass it to the `<Admin>` component as the `notification` prop: -```jsx +```tsx // in src/App.js import MyNotification from './MyNotification'; import dataProvider from './dataProvider'; @@ -646,7 +646,7 @@ React-admin uses [react-query](https://react-query-v3.tanstack.com/) to fetch, c If you want to override the react-query default query and mutation default options, or use a specific client or mutation cache, you can create your own `QueryClient` instance and pass it to the `<Admin queryClient>` prop: -```jsx +```tsx import { Admin } from 'react-admin'; import { QueryClient } from 'react-query'; @@ -673,7 +673,7 @@ To know which options you can pass to the `QueryClient` constructor, check the [ The common settings that react-admin developers often overwrite are: -```jsx +```tsx import { QueryClient } from 'react-query'; const queryClient = new QueryClient({ @@ -712,7 +712,7 @@ When you run an `<Admin>` with no child `<Resource>` nor `<CustomRoutes>`, react You can replace that "ready" screen by passing a custom component as the `ready` prop: -```jsx +```tsx import * as React from 'react'; import { Admin } from 'react-admin'; @@ -736,7 +736,7 @@ Some pages in react-admin apps may allow anonymous access. For that reason, reac If you know your app will never accept anonymous access, you can force the app to wait for the `authProvider.checkAuth()` to resolve before rendering the page layout, by setting the `<Admin requireAuth>` prop. -```jsx +```tsx const App = () => ( <Admin dataProvider={dataProvider} authProvider={authProvider} requireAuth> <Resource name="posts" list={PostList} /> @@ -750,7 +750,7 @@ The `<Admin>` component initializes a [Store](./Store.md) for user preferences u For instance, you can store the data in memory instead of `localStorage`. This is useful e.g. for tests, or for apps that should not persist user data between sessions: -```jsx +```tsx import { Admin, Resource, memoryStore } from 'react-admin'; const App = () => ( @@ -768,7 +768,7 @@ Material UI supports [theming](https://mui.com/material-ui/customization/theming For instance, to use a dark theme by default: -```jsx +```tsx import { defaultTheme } from 'react-admin'; const theme = { @@ -793,7 +793,7 @@ For more details on predefined and custom themes, refer to [the Application Them On error pages, the header of an admin app uses 'React Admin' as the main app title. Use the `title` to customize it. -```jsx +```tsx const App = () => ( <Admin title="My Custom Admin" dataProvider={simpleRestProvider('http://path.to.my.api')}> // ... @@ -807,7 +807,7 @@ The [`children`](#children) prop of the `<Admin>` component define the routes of In addition to [`<Resource> elements`](./Resource.md) for CRUD pages, you can use [the `<CustomRoutes>` component](./CustomRoutes.md) to do add custom routes. -```jsx +```tsx // in src/App.js import * as React from "react"; import { Route } from 'react-router-dom'; @@ -837,7 +837,7 @@ React-admin uses [the react-router library](https://reactrouter.com/) to handle But you may want to use another routing strategy, e.g. to allow server-side rendering of individual pages. React-router offers various Router components to implement such routing strategies. If you want to use a different router, simply wrap it around your app. React-admin will detect that it's already inside a router, and skip its own router. -```jsx +```tsx import { BrowserRouter } from 'react-router-dom'; import { Admin, Resource } from 'react-admin'; @@ -856,7 +856,7 @@ React-admin links are absolute (e.g. `/posts/123/show`). If you serve your admin However, if you serve your admin from a sub path AND use another Router (like `BrowserRouter` for instance), you need to set the `<Admin basename>` prop, so that react-admin routes include the basename in all links (e.g. `/admin/posts/123/show`). -```jsx +```tsx import { Admin, Resource } from 'react-admin'; const App = () => ( @@ -878,7 +878,7 @@ If you want to use react-admin as a sub path of a larger React application, chec You can include a react-admin app inside another app, using a react-router `<Route>`: -```jsx +```tsx import { BrowserRouter, Routes, Route } from 'react-router-dom'; import { StoreFront } from './StoreFront'; import { StoreAdmin } from './StoreAdmin'; @@ -895,7 +895,7 @@ export const App = () => ( React-admin will have to prefix all the internal links with `/admin`. Use the `<Admin basename>` prop for that: -```jsx +```tsx // in src/StoreAdmin.js import { Admin, Resource } from 'react-admin'; @@ -918,7 +918,7 @@ The `<Admin>` component accepts a function as one of its children and this funct For instance, getting the resource from an API might look like: -```jsx +```tsx import * as React from "react"; import { Admin, Resource } from 'react-admin'; import simpleRestProvider from 'ra-data-simple-rest'; @@ -931,7 +931,7 @@ const knownResources = [ <Resource name="comments" list={CommentList} />, ]; -const fetchResources = permissions => +const fetchResources = (permissions: string) => fetch('https://myapi/resources', { method: 'POST', headers: { @@ -957,46 +957,58 @@ So it's impossible, for instance, to have a dynamic list of resources based on a To overcome this limitation, you can build your own `<Admin>` component using two lower-level components: `<AdminContext>` (responsible for putting the providers in contexts) and `<AdminUI>` (responsible for displaying the UI). Through this approach you'll have to bring your own i18n provider and store. Luckily react-admin provides easy to use defaults for you. Here is an example: -``` jsx -import * as React from 'react'; -import { useEffect, useState } from 'react'; +```tsx +import * as React from "react"; +import { useEffect, useState } from "react"; import { - AdminContext, - AdminUI, - defaultI18nProvider, - localStorageStore, - Resource, - ListGuesser, - Loading, - useDataProvider, -} from 'react-admin'; + AdminContext, + AdminUI, + DataProvider, + defaultI18nProvider, + localStorageStore, + Resource, + ListGuesser, + Loading, + useDataProvider, +} from "react-admin"; +import myDataProvider from "./myDataProvider"; const store = localStorageStore(); function App() { - return ( - <AdminContext dataProvider={myDataProvider} i18nProvider={defaultI18nProvider} store={store}> - <AsyncResources /> - </AdminContext> - ); + return ( + <AdminContext + dataProvider={myDataProvider} + i18nProvider={defaultI18nProvider} + store={store} + > + <AsyncResources /> + </AdminContext> + ); } -function AsyncResources() { - const [resources, setResources] = useState([]); - const dataProvider = useDataProvider(); - - useEffect(() => { - // Note that the `getResources` is not provided by react-admin. You have to implement your own custom verb. - dataProvider.getResources().then(r => setResources(r)); - }, []); +interface MyDataProvider extends DataProvider { + getResources: () => Promise<{ name: string }[]>; +} - return ( - <AdminUI ready={Loading}> - {resources.map(resource => ( - <Resource name={resource.name} key={resource.key} list={ListGuesser} /> - ))} - </AdminUI> - ); +function AsyncResources() { + const [resources, setResources] = useState<Array<{ name: string }>>( + [] as Array<{ name: string }> + ); + const dataProvider = useDataProvider<MyDataProvider>(); + + useEffect(() => { + // Note that the `getResources` is not provided by react-admin. You have to implement your own custom verb. + dataProvider.getResources().then((r) => setResources(r)); + }, []); + + return ( + <AdminUI ready={Loading}> + {resources.map((resource) => ( + <Resource name={resource.name} key={resource.name} list={ListGuesser} /> + ))} + </AdminUI> + ); } ``` From 69d7588f644e68285e411eab9d6a4fa697d4ed88 Mon Sep 17 00:00:00 2001 From: Gildas Garcia <1122076+djhi@users.noreply.github.com> Date: Tue, 22 Aug 2023 15:19:42 +0200 Subject: [PATCH 2/2] Apply review suggestions --- docs/Admin.md | 74 +++++++++++++++++++++++++-------------------------- 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/docs/Admin.md b/docs/Admin.md index ef88b4f7471..1e3bceeedcc 100644 --- a/docs/Admin.md +++ b/docs/Admin.md @@ -931,7 +931,7 @@ const knownResources = [ <Resource name="comments" list={CommentList} />, ]; -const fetchResources = (permissions: string) => +const fetchResources = (permissions: any) => fetch('https://myapi/resources', { method: 'POST', headers: { @@ -961,54 +961,54 @@ To overcome this limitation, you can build your own `<Admin>` component using tw import * as React from "react"; import { useEffect, useState } from "react"; import { - AdminContext, - AdminUI, - DataProvider, - defaultI18nProvider, - localStorageStore, - Resource, - ListGuesser, - Loading, - useDataProvider, + AdminContext, + AdminUI, + DataProvider, + defaultI18nProvider, + localStorageStore, + Resource, + ListGuesser, + Loading, + useDataProvider, } from "react-admin"; import myDataProvider from "./myDataProvider"; const store = localStorageStore(); function App() { - return ( - <AdminContext - dataProvider={myDataProvider} - i18nProvider={defaultI18nProvider} - store={store} - > - <AsyncResources /> - </AdminContext> - ); + return ( + <AdminContext + dataProvider={myDataProvider} + i18nProvider={defaultI18nProvider} + store={store} + > + <AsyncResources /> + </AdminContext> + ); } interface MyDataProvider extends DataProvider { - getResources: () => Promise<{ name: string }[]>; + getResources: () => Promise<{ name: string }[]>; } function AsyncResources() { - const [resources, setResources] = useState<Array<{ name: string }>>( - [] as Array<{ name: string }> - ); - const dataProvider = useDataProvider<MyDataProvider>(); - - useEffect(() => { - // Note that the `getResources` is not provided by react-admin. You have to implement your own custom verb. - dataProvider.getResources().then((r) => setResources(r)); - }, []); - - return ( - <AdminUI ready={Loading}> - {resources.map((resource) => ( - <Resource name={resource.name} key={resource.name} list={ListGuesser} /> - ))} - </AdminUI> - ); + const [resources, setResources] = useState<Array<{ name: string }>>( + [] as Array<{ name: string }> + ); + const dataProvider = useDataProvider<MyDataProvider>(); + + useEffect(() => { + // Note that the `getResources` is not provided by react-admin. You have to implement your own custom verb. + dataProvider.getResources().then((r) => setResources(r)); + }, []); + + return ( + <AdminUI ready={Loading}> + {resources.map((resource) => ( + <Resource name={resource.name} key={resource.name} list={ListGuesser} /> + ))} + </AdminUI> + ); } ```