Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Doc] Add headless section in pages components #9447

Merged
merged 11 commits into from
Nov 22, 2023
89 changes: 89 additions & 0 deletions docs/Create.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ const App = () => (
export default App;
```

## Props

You can customize the `<Create>` component using the following props:

* [`actions`](#actions): override the actions toolbar with a custom component
Expand Down Expand Up @@ -599,3 +601,90 @@ export default OrderEdit;
```

**Tip:** If you'd like to avoid creating an intermediate component like `<CityInput>`, or are using an `<ArrayInput>`, you can use the [`<FormDataConsumer>`](./Inputs.md#linking-two-inputs) component as an alternative.

## Controlled Mode
Copy link
Collaborator

@djhi djhi Nov 21, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why Controlled Mode? This is misleading imo. Controlled vs Uncontrolled refers to other concepts

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have no better name for it...

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With Default UI ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand what that means... I don't think it's better than "controlled mode"

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nvm


`<Create>` deduces the resource and the initial form values from the URL. This is fine for a creation page, but if you need to let users create records from another page, you probably want to define this parameter yourself.

In that case, use the [`resource`](#resource) and [`record`](#record) props to set the creation parameters regardless of the URL.

```jsx
import { Create, SimpleForm, TextInput, SelectInput } from "react-admin";

export const BookCreate = () => (
<Create resource="books" redirect={false}>
<SimpleForm>
<TextInput source="title" />
<TextInput source="author" />
<SelectInput source="availability" choices={[
{ id: "in_stock", name: "In stock" },
{ id: "out_of_stock", name: "Out of stock" },
{ id: "out_of_print", name: "Out of print" },
]} />
</SimpleForm>
</Create>
);
```

**Tip**: You probably also want to customize [the `redirect` prop](#redirect) if you embed an `<Create>` component in another page.

## Headless Version

Besides preparing a save handler, `<Create>` renders the default creation page layout (title, actions, a Material UI `<Card>`) and its children. If you need a custom creation layout, you may prefer [the `<CreateBase>` component](./CreateBase.md), which only renders its children in a [`CreateContext`](./useCreateContext.md).

```jsx
import { CreateBase, SelectInput, SimpleForm, TextInput, Title } from "react-admin";
import { Card, CardContent, Container } from "@mui/material";

export const BookCreate = () => (
<CreateBase>
<Container>
<Title title="Create book" />
<Card>
<CardContent>
<SimpleForm>
<TextInput source="title" />
<TextInput source="author" />
<SelectInput source="availability" choices={[
{ id: "in_stock", name: "In stock" },
{ id: "out_of_stock", name: "Out of stock" },
{ id: "out_of_print", name: "Out of print" },
]} />
</SimpleForm>
</CardContent>
</Card>
</Container>
</CreateBase>
);
```

In the previous example, `<SimpleForm>` grabs the save handler from the `CreateContext`.

If you don't need the `CreateContext`, you can use [the `useCreateController` hook](./useCreateController.md), which does the same data fetching as `<CreateBase>` but lets you render the content.

```jsx
import { useCreateController, SelectInput, SimpleForm, TextInput, Title } from "react-admin";
import { Card, CardContent, Container } from "@mui/material";

export const BookCreate = () => {
const { save } = useCreateController();
return (
<Container>
<Title title="Create book" />
<Card>
<CardContent>
<SimpleForm onSubmit={values => save(values)}>
<TextInput source="title" />
<TextInput source="author" />
<SelectInput source="availability" choices={[
{ id: "in_stock", name: "In stock" },
{ id: "out_of_stock", name: "Out of stock" },
{ id: "out_of_print", name: "Out of print" },
]} />
</SimpleForm>
</CardContent>
</Card>
</Container>
);
};
```
6 changes: 4 additions & 2 deletions docs/CreateBase.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ title: "The CreateBase Component"

# `<CreateBase>`

The `<CreateBase>` component is a headless version of `<Create>`: it prepares a form submit handler, and renders its children.
`<CreateBase>` is a headless variant of [`<Create>`](./Create.md). It prepares a form submit handler, and renders its children in a [`CreateContext`](./useCreateContext.md). Use it to build a custom creation page layout.

It does that by calling `useCreateController`, and by putting the result in an `CreateContext`.
Contrary to [`<Create>`](./Create.md), it does not render the page layout, so no title, no actions, and no `<Card>`.

`<CreateBase>` relies on the [`useCreateController`](./useCreateController.md) hook.

## Usage

Expand Down
89 changes: 89 additions & 0 deletions docs/Edit.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ const App = () => (
export default App;
```

## Props

You can customize the `<Edit>` component using the following props:

* [`actions`](#actions): override the actions toolbar with a custom component
Expand Down Expand Up @@ -770,3 +772,90 @@ export const PostEdit = () => (
```

**Tips:** If you want users to be warned if they haven't pressed the Save button when they browse to another record, you can follow the tutorial [Navigating Through Records In`<Edit>` Views](./PrevNextButtons.md#navigating-through-records-in-edit-views-after-submit).

## Controlled Mode

`<Edit>` deduces the resource and the record id from the URL. This is fine for an edition page, but if you need to let users edit records from another page, you probably want to define the edit parameters yourself.

In that case, use the [`resource`](#resource) and [`id`](#id) props to set the edit parameters regardless of the URL.

```jsx
import { Edit, SimpleForm, TextInput, SelectInput } from "react-admin";

export const BookEdit = ({ id }) => (
<Edit resource="books" id={id} redirect={false}>
<SimpleForm>
<TextInput source="title" />
<TextInput source="author" />
<SelectInput source="availability" choices={[
{ id: "in_stock", name: "In stock" },
{ id: "out_of_stock", name: "Out of stock" },
{ id: "out_of_print", name: "Out of print" },
]} />
</SimpleForm>
</Edit>
);
```

**Tip**: You probably also want to customize [the `redirect` prop](#redirect) if you embed an `<Edit>` component in another page.

## Headless Version

Besides fetching a record and preparing a save handler, `<Edit>` renders the default edition page layout (title, actions, a Material UI `<Card>`) and its children. If you need a custom edition layout, you may prefer [the `<EditBase>` component](./EditBase.md), which only renders its children in an [`EditContext`](./useEditContext.md).

```jsx
import { EditBase, SelectInput, SimpleForm, TextInput, Title } from "react-admin";
import { Card, CardContent, Container } from "@mui/material";

export const BookEdit = () => (
<EditBase>
<Container>
<Title title="Book Edition" />
<Card>
<CardContent>
<SimpleForm>
<TextInput source="title" />
<TextInput source="author" />
<SelectInput source="availability" choices={[
{ id: "in_stock", name: "In stock" },
{ id: "out_of_stock", name: "Out of stock" },
{ id: "out_of_print", name: "Out of print" },
]} />
</SimpleForm>
</CardContent>
</Card>
</Container>
</EditBase>
);
```

In the previous example, `<SimpleForm>` grabs the record and the save handler from the `EditContext`.

If you don't need the `EditContext`, you can use [the `useEditController` hook](./useEditController.md), which does the same data fetching as `<EditBase>` but lets you render the content.

```tsx
import { useEditController, SelectInput, SimpleForm, TextInput, Title } from "react-admin";
import { Card, CardContent, Container } from "@mui/material";

export const BookEdit = () => {
const { record, save } = useEditController();
return (
<Container>
<Title title={`Edit book ${record?.title}`} />
<Card>
<CardContent>
<SimpleForm record={record} onSubmit={values => save(values)}>
<TextInput source="title" />
<TextInput source="author" />
<SelectInput source="availability" choices={[
{ id: "in_stock", name: "In stock" },
{ id: "out_of_stock", name: "Out of stock" },
{ id: "out_of_print", name: "Out of print" },
]} />
</SimpleForm>
</CardContent>
</Card>
</Container>
);
};
```
37 changes: 21 additions & 16 deletions docs/EditBase.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,39 +5,44 @@ title: "The EditBase Component"

# `<EditBase>`

The `<EditBase>` component is a headless version of [`<Edit>`](./Edit.md): it fetches a record based on the URL, prepares a form submit handler, and renders its children.
`<EditBase>` is a headless variant of [`<Edit>`](./Edit.md): it fetches a record based on the URL, prepares a form submit handler, and renders its children inside an [`EditContext`](./useEditContext.md). Use it to build a custom edition page layout.

It does that by calling [`useEditController`](./useEditController.md), and by putting the result in an `EditContext`.
Contrary to [`<Edit>`](./Edit.md), it does not render the page layout, so no title, no actions, and no `<Card>`.

`<EditBase>` relies on the [`useEditController`](./useEditController.md) hook.

## Usage

Use `<EditBase>` to create a custom Edition view, with exactly the content you add as child and nothing else (no title, Card, or list of actions as in the `<Edit>` component).

```jsx
import * as React from "react";
import { EditBase, SimpleForm, TextInput, SelectInput } from "react-admin";
import { Card } from "@mui/material";
import { EditBase, SelectInput, SimpleForm, TextInput, Title } from "react-admin";
import { Card, CardContent, Container } from "@mui/material";

export const BookEdit = () => (
<EditBase>
<div>
<Container>
<Title title="Book Edition" />
<Card>
<SimpleForm>
<TextInput source="title" />
<TextInput source="author" />
<SelectInput source="availability" choices={[
{ id: "in_stock", name: "In stock" },
{ id: "out_of_stock", name: "Out of stock" },
{ id: "out_of_print", name: "Out of print" },
]} />
</SimpleForm>
<CardContent>
<SimpleForm>
<TextInput source="title" />
<TextInput source="author" />
<SelectInput source="availability" choices={[
{ id: "in_stock", name: "In stock" },
{ id: "out_of_stock", name: "Out of stock" },
{ id: "out_of_print", name: "Out of print" },
]} />
</SimpleForm>
</CardContent>
</Card>
</div>
</Container>
</EditBase>
);
```

## Props

You can customize the `<EditBase>` component using the following props, documented in the `<Edit>` component:

* `children`: the components that renders the form
Expand Down
87 changes: 87 additions & 0 deletions docs/Features.md
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,93 @@ We have made many improvements to this default layout based on user feedback. In

And for mobile users, react-admin renders a different layout with larger margins and less information density (see [Responsive](#responsive)).

## Headless

React-admin components use Material UI components by default, which lets you scaffold a page in no time. As material UI supports [theming](#theming), you can easily customize the look and feel of your app. But in some cases, this is not enough, and you need to use another UI library.

You can change the UI library you use with react-admin to use [Ant Design](https://ant.design/), [Daisy UI](https://daisyui.com/), [Chakra UI](https://chakra-ui.com/), or even you own custom UI library. The **headless logic** behind react-admin components is agnostic of the UI library, and is exposed via `...Base` components and controller hooks.

For instance, here a List view built with [Ant Design](https://ant.design/):

![List view built with Ant Design](./img/list_ant_design.png)

It leverages the `useListController` hook:

{% raw %}
```jsx
import { useListController } from 'react-admin';
import { Card, Table, Button } from 'antd';
import {
CheckCircleOutlined,
PlusOutlined,
EditOutlined,
} from '@ant-design/icons';
import { Link } from 'react-router-dom';

const PostList = () => {
const { data, page, total, setPage, isLoading } = useListController({
sort: { field: 'published_at', order: 'DESC' },
perPage: 10,
});
const handleTableChange = (pagination) => {
setPage(pagination.current);
};
return (
<>
<div style={{ margin: 10, textAlign: 'right' }}>
<Link to="/posts/create">
<Button icon={<PlusOutlined />}>Create</Button>
</Link>
</div>
<Card bodyStyle={{ padding: '0' }} loading={isLoading}>
<Table
size="small"
dataSource={data}
columns={columns}
pagination={{ current: page, pageSize: 10, total }}
onChange={handleTableChange}
/>
</Card>
</>
);
};

const columns = [
{ title: 'Id', dataIndex: 'id', key: 'id' },
{ title: 'Title', dataIndex: 'title', key: 'title' },
{
title: 'Publication date',
dataIndex: 'published_at',
key: 'pub_at',
render: (value) => new Date(value).toLocaleDateString(),
},
{
title: 'Commentable',
dataIndex: 'commentable',
key: 'commentable',
render: (value) => (value ? <CheckCircleOutlined /> : null),
},
{
title: 'Actions',
render: (_, record) => (
<Link to={`/posts/${record.id}`}>
<Button icon={<EditOutlined />}>Edit</Button>
</Link>
),
},
];

export default PostList;
```
{% endraw %}

Check the following hooks to learn more about headless controllers:

- [`useListController`](./useListController.md)
- [`useEditController`](./useEditController.md)
- [`useCreateController`](./useCreateController.md)
- [`useShowController`](./useShowController.md)

## Guessers & Scaffolding

When mapping a new API route to a CRUD view, adding fields one by one can be tedious. React-admin provides a set of guessers that can automatically **generate a complete CRUD UI based on an API response**.
Expand Down
4 changes: 3 additions & 1 deletion docs/Form.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ title: "Form"

# `<Form>`

The `<Form>` component creates a `<form>` to edit a record, and renders its children. It is a headless component used internally by `<SimpleForm>`, `<TabbedForm>`, and other form components.
`<Form>` is a headless component that creates a `<form>` to edit a record, and renders its children. Use it to build a custom form layout, or to use another UI kit than Material UI.

`<Form>` reads the `record` from the `RecordContext`, uses it to initialize the defaultValues of a react-hook-form via `useForm`, turns the `validate` function info a react-hook-form compatible form validator, notifies the user when the input validation fails, and creates a form context via `<FormProvider>`.

`<Form>` is used internally by `<SimpleForm>`, `<TabbedForm>`, and other form components.

## Usage

Use `<Form>` to build completely custom form layouts. Don't forget to include a submit button (or react-admin's [`<SaveButton>`](./SaveButton.md)) to actually save the record.
Expand Down
Loading
Loading