Skip to content

Commit

Permalink
Merge pull request #4608 from marmelab/document-controller-hooks
Browse files Browse the repository at this point in the history
Document page controller hooks
  • Loading branch information
fzaninotto authored Mar 31, 2020
2 parents d41c2ef + 0ea27da commit bfc6adc
Show file tree
Hide file tree
Showing 5 changed files with 253 additions and 6 deletions.
107 changes: 107 additions & 0 deletions docs/CreateEdit.md
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,113 @@ React-admin provides guessers for the `List` view (`ListGuesser`), the `Edit` vi
**Tip**: Do not use the guessers in production. They are slower than manually-defined components, because they have to infer types based on the content. Besides, the guesses are not always perfect.
## `useCreateController` and `useEditController`
The `<Create>` and `<Edit>` components both take care of two things:
1. (the "controller") Fetching data based on the URL and transforming it
2. (the "view") Rendering the page title, the actions, the content and aside areas
In some cases, you may want to customize the view entirely (i.e. keep the code for step 1, and provide your own code for step 2). For these cases, react-admin provides two hooks, `useCreateController()` and `useEditController()`. These hooks contain just the controller part of the `<Create>` and `<Edit>` components.
**Tip**: You should not use these hooks to hide or show form inputs based on the data. For that need, check [`<FormDataConsumer>`](./Inputs.md#linking-two-inputs)
### `useCreateController`
This hook takes one object as input (the props passed to a `<Create>` component) and returns the save callback for the Create view, as well as some pre-computed values. You can use it to create your own custom Create view, like this one:
```jsx
import { useCreateController, SimpleForm } from 'react-admin';

const MyCreate = props => {
const {
basePath, // deduced from the location, useful for action buttons
defaultTitle, // the translated title based on the resource, e.g. 'Create Post'
record, // empty object, unless some values were passed in the location state to prefill the form
redirect, // the default redirection route. Defaults to 'edit', unless the resource has no edit view, in which case it's 'list'
resource, // the resource name, deduced from the location. e.g. 'posts'
save, // the create callback, to be passed to the underlying form as submit handler
saving, // boolean that becomes true when the dataProvider is called to create the record
version, // integer used by the refresh feature
} = useCreateController(props);
return (
<div>
<h1>{defaultTitle}</h1>
{cloneElement(props.children, {
basePath,
record,
redirect,
resource,
save,
saving,
version,
})}
</div>
);
}

const PostCreate = props => (
<MyCreate {...props}>
<SimpleForm>
...
</SimpleForm>
</MyCreate>
)
```
This custom Create view has no action buttons or aside component - it's up to you to add them in pure React.
**Tip**: You don't have to clone the child element. If you can't reuse an existing form component like `<SimpleForm>` or `<TabbedForm>`, feel free to write the form code inside your custom `MyCreate` component.
### `useEditController`
This hook takes one object as input (the props passed to an `<Edit>` component) and returns the fetched data and callbacks for the Edit view. You can use it to create your own custom Edit view, like this one:
```jsx
import { useEditController, SimpleForm } from 'react-admin';

const MyEdit = props => {
const {
basePath, // deduced from the location, useful for action buttons
defaultTitle, // the translated title based on the resource, e.g. 'Post #123'
loaded, // boolean that is false until the record is available
loading, // boolean that is true on mount, and false once the record was fetched
record, // record fetched via dataProvider.getOne() based on the id from the location
redirect, // the default redirection route. Defaults to 'list'
resource, // the resource name, deduced from the location. e.g. 'posts'
save, // the update callback, to be passed to the underlying form as submit handler
saving, // boolean that becomes true when the dataProvider is called to update the record
version, // integer used by the refresh feature
} = useEditController(props);
return (
<div>
<h1>{defaultTitle}</h1>
{cloneElement(props.children, {
basePath,
record,
redirect,
resource,
save,
saving,
version,
})}
</div>
);
}

const PostEdit = props => (
<MyEdit {...props}>
<SimpleForm>
...
</SimpleForm>
</MyEdit>
)
```
This custom Edit view has no action buttons or aside component - it's up to you to add them in pure React.
**Tip**: You don't have to clone the child element. If you can't reuse an existing form component like `<SimpleForm>` or `<TabbedForm>`, feel free to write the form code inside your custom `MyEdit` component.
## The `<SimpleForm>` component
The `<SimpleForm>` component receives the `record` as prop from its parent component. It is responsible for rendering the actual form. It is also responsible for validating the form data. Finally, it receives a `handleSubmit` function as prop, to be called with the updated record as argument when the user submits the form.
Expand Down
90 changes: 90 additions & 0 deletions docs/List.md
Original file line number Diff line number Diff line change
Expand Up @@ -797,6 +797,96 @@ React-admin provides guessers for the `List` view (`ListGuesser`), the `Edit` vi

**Tip**: Do not use the guessers in production. They are slower than manually-defined components, because they have to infer types based on the content. Besides, the guesses are not always perfect.

## `useListController`

The `<List>` components takes care of two things:

1. (the "controller") Fetching data based on the URL and transforming it
2. (the "view") Rendering the page title, the actions, the content and aside areas

In some cases, you may want to customize the view entirely (i.e. keep the code for step 1, and provide your own code for step 2). For these cases, react-admin provides a hook called `useListController()`, which contain just the controller part of the `<List>`.

This hook takes one object as input (the props passed to a `<List>` component) and returns the fetched data and callbacks for the List view. In fact, it returns a lot of variables because the List page is complex: based on the URL, the List controller deduces filters, pagination, ordering, it provides callbacks to update them, it fetches the data, etc.

You can use `useListController()` to create your own custom List view, like this one:

```jsx
import {
useListController,
ListToolbar,
BulkActionsToolbar,
Pagination,
Datagrid
} from 'react-admin';

const MyList = props => {
const controllerProps = useListController(props);
const {
// fetched data
data, // an id-based dictionary of the list data, e.g. { 123: { id: 123, title: 'hello world' }, 456: { ... } }
ids, // an array listing the ids of the records in the list, e.g [123, 456, ...]
total, // the total number of results for the current filters, excluding pagination. Useful to build the pagination controls. e.g. 23
loaded, // boolean that is false until the data is available
loading, // boolean that is true on mount, and false once the data was fetched
// pagination
page, // the current page. Starts at 1
perPage, // the number of results per page. Defaults to 25
setPage, // a callback to change the page, e.g. setPage(3)
setPerPage, // a callback to change the number of results per page, e.g. setPerPage(25)
// sorting
currentSort, // a sort object { field, order }, e.g. { field: 'date', order: 'DESC' }
setSort, // a callback to change the sort, e.g. setSort('name', 'ASC')
// filtering
displayedFilters, // a dictionary of the displayed filters, e.g. { title: true, nationality: true }
filterValues, // a dictionary of filter values, e.g. { title: 'lorem', nationality: 'fr' }
setFilters, // a callback to update the filters, e.g. setFilters(filters, displayedFilters)
showFilter, // a callback to show one of the filters, e.g. showFilter('title', defaultValue)
hideFilter, // a callback to hide one of the filters, e.g. hidefilter('title')
// row selection
selectedIds, // an array listing the ids of the selcted rows, e.g. [123, 456]
onSelect, // callback to change the list of selected rows, e.g onSelect([456, 789])
onToggleItem, // callback to toggle the selection of a given record based on its id, e.g. onToggleItem(456)
onUnselectItems, // callback to clear the selection, e.g. onUnselectItems();
// misc
basePath, // deduced from the location, useful for action buttons
defaultTitle, // the translated title based on the resource, e.g. 'Posts'
resource, // the resource name, deduced from the location. e.g. 'posts'
version, // integer used by the refresh feature
} = controllerProps;
return (
<div>
<h1>{defaultTitle}</h1>
<ListToolbar
filters={props.filters}
{...controllerProps}
actions={props.actions}
permanentFilter={props.filter}
/>
<BulkActionsToolbar {...controllerProps}>
{props.bulkActionButtons}
</BulkActionsToolbar>
{cloneElement(children, {
...controllerProps,
hasBulkActions: props.bulkActionButtons !== false,
})}
<Pagination {...controllerProps} />
</div>
);
}

const PostList = props => (
<MyList {...props}>
<Datagrid>
...
</Datagrid>
</MyList>
)
```

This custom List view has no aside component - it's up to you to add it in pure React.

**Tip**: You don't have to clone the child element. If you can't reuse an existing list view component like `<Datagrid>` or `<SimpleList>`, feel free to write the form code inside your custom `MyList` component.

## The `<Datagrid>` component

The `Datagrid` component renders a list of records as a table. It is usually used as a child of the [`<List>`](#the-list-component) and [`<ReferenceManyField>`](./Fields.md#referencemanyfield) components.
Expand Down
8 changes: 4 additions & 4 deletions docs/Reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,11 +106,11 @@ title: "Reference"
* `useCheckAuth`
* `useChoices`
* [`useCreate`](./Actions.md#specialized-hooks)
* `useCreateController`
* [`useCreateController`](./CreateEdit.md#usecreatecontroller)
* [`useDataProvider`](./Actions.md#usedataprovider-hook)
* [`useDelete`](./Actions.md#specialized-hooks)
* [`useDeleteMany`](./Actions.md#specialized-hooks)
* `useEditController`
* [`useEditController`](./CreateEdit.md#useeditcontroller)
* `useFilterState`
* [`useGetList`](./Actions.md#specialized-hooks)
* [`useGetMany`](./Actions.md#specialized-hooks)
Expand All @@ -120,7 +120,7 @@ title: "Reference"
* [`useGetOne`](./Actions.md#specialized-hooks)
* `useGetPermissions`
* `useInput`
* `useListController`
* [`useListController`](./List.md#uselistcontroller)
* `useListParams`
* `useLoading`
* [`useLocale`](./Translation.md#uselocale-getting-the-current-locale)
Expand All @@ -142,7 +142,7 @@ title: "Reference"
* `useReferenceManyFieldController`
* [`useRefresh`](./Actions.md#handling-side-effects-in-usedataprovider)
* [`useSetLocale`](./Translation.md#usesetlocale-changing-locale-at-runtime)
* `useShowController`
* [`useShowController`](./Show.md#useshowcontroller)
* `useSortState`
* [`useStyles`](./Theming.md#overriding-a-component-style)
* `useSuggestions`
Expand Down
50 changes: 50 additions & 0 deletions docs/Show.md
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,56 @@ React-admin provides guessers for the `List` view (`ListGuesser`), the `Edit` vi

**Tip**: Do not use the guessers in production. They are slower than manually-defined components, because they have to infer types based on the content. Besides, the guesses are not always perfect.

## `useShowController`

The `<Show>` component takes care of two things:

1. (the "controller") Fetching data based on the URL and transforming it
2. (the "view") Rendering the page title, the actions, the content and aside areas

In some cases, you may want to customize the view entirely (i.e. keep the code for step 1, and provide your own code for step 2). For these cases, react-admin provides a hook called `useShowController()`, which contains just the controller part of the `<Show>` component.

This hook takes one object as input (the props passed to a `<Show>` component) and returns the fetched data for the Show view. You can use it to create your own custom Show view, like this one:

```jsx
import { useShowController, SimpleShowLayout } from 'react-admin';

const MyShow = props => {
const {
basePath, // deduced from the location, useful for action buttons
defaultTitle, // the translated title based on the resource, e.g. 'Post #123'
loaded, // boolean that is false until the record is available
loading, // boolean that is true on mount, and false once the record was fetched
record, // record fetched via dataProvider.getOne() based on the id from the location
resource, // the resource name, deduced from the location. e.g. 'posts'
version, // integer used by the refresh feature
} = useShowController(props);
return (
<div>
<h1>{defaultTitle}</h1>
{cloneElement(props.children, {
basePath,
record,
resource,
version,
})}
</div>
);
}

const PostShow = props => (
<MyShow {...props}>
<SimpleShowLayout>
...
</SimpleShowLayout>
</MyShow>
)
```

This custom Show view has no action buttons or aside component - it's up to you to add them in pure React.

**Tip**: You don't have to clone the child element. If you can't reuse an existing form component like `<SimpleShowLayout>`, feel free to write the form code inside your custom `MyShow` component.

## The `<SimpleShowLayout>` component

The `<SimpleShowLayout>` component receives the `record` as prop from its parent component. It is responsible for rendering the actual view.
Expand Down
4 changes: 2 additions & 2 deletions docs/navigation.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@
</a></li>
<li {% if page.path contains 'List.md' %} class="active" {% endif %}><a href="./List.html"><code>&lt;List&gt;</code>
View</a></li>
<li {% if page.path contains 'Show.md' %} class="active" {% endif %}><a href="./Show.html"><code>&lt;Show&gt;</code>
View</a></li>
<li {% if page.path contains 'CreateEdit.md' %} class="active" {% endif %}><a
href="./CreateEdit.html"><code>&lt;Create&gt;</code>
and <code>&lt;Edit&gt;</code> Views</a>
</li>
<li {% if page.path contains 'Show.md' %} class="active" {% endif %}><a href="./Show.html"><code>&lt;Show&gt;</code>
View</a></li>
<li {% if page.path contains 'Fields.md' %} class="active" {% endif %}><a
href="./Fields.html"><code>&lt;Field&gt;</code>
Components</a></li>
Expand Down

0 comments on commit bfc6adc

Please sign in to comment.