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

Feature: prev next button for detail and edit views #9165

Merged
merged 91 commits into from
Aug 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
91 commits
Select commit Hold shift + click to select a range
299d435
add ListIdsContext, its provider and its hook to use it
adguernier Aug 7, 2023
1650dfb
get list with filters from resource context and keep only the ids to …
adguernier Aug 7, 2023
1d4053f
rename useLIstIdsContext to useListIdsContext
adguernier Aug 7, 2023
10fc871
export ListIdsContext modules
adguernier Aug 7, 2023
7c64574
naive implementation of PrexNextButton
adguernier Aug 7, 2023
ce2aa24
Merge remote-tracking branch 'origin/next' into feat-prev-next-button…
adguernier Aug 10, 2023
b6fbf0b
add ListIdsContextProvider in edit and show base
adguernier Aug 10, 2023
fd41919
wip: add basic story
adguernier Aug 10, 2023
bf81d19
add a type for ListIdsContext and use an object to store data
adguernier Aug 10, 2023
4fd161b
add total and the index of current item
adguernier Aug 10, 2023
3296703
remove useless return type
adguernier Aug 10, 2023
20ebf30
remove useless import
adguernier Aug 10, 2023
a0dd8fe
disable navigation button on list end
adguernier Aug 10, 2023
0675e50
add a linkType props to specify where to go
adguernier Aug 11, 2023
8c3fe37
remove every code about ListIdsContext... to delegate the logic in P…
adguernier Aug 11, 2023
d31e130
use Link component instead onClick handler to display link target on …
adguernier Aug 11, 2023
f6dc078
translate previous and next navigation
adguernier Aug 11, 2023
71fcedb
wrap button into nav component
adguernier Aug 11, 2023
c4f5718
add sx prop
adguernier Aug 11, 2023
fe01ff9
use useStore hook to get list params and fetch data
adguernier Aug 14, 2023
179e3fc
add limit prop to limit the query
adguernier Aug 14, 2023
888c850
display a large amount of data with data-generator-retail
adguernier Aug 14, 2023
3fe032d
use useRecordContext hook instead of useGetRecordId to prevent error …
adguernier Aug 14, 2023
b891867
enable previous button even if index is 0
adguernier Aug 14, 2023
279a59c
set default limit to 1000 because useGetList set a perPage to 25 by d…
adguernier Aug 14, 2023
fbca733
merge sort, order and filter params with listParams
adguernier Aug 14, 2023
b041b4c
move PrevNextButton in TopToolbar
adguernier Aug 14, 2023
eb8e6b9
store duplicated inputs and fields in const
adguernier Aug 14, 2023
4b62e29
set prev and next props button conditionally
adguernier Aug 14, 2023
202f70a
add staleTime props to avoid calls getList at every page
adguernier Aug 14, 2023
9ba26e5
add MemoryRouter to stories to reset views
adguernier Aug 14, 2023
5b77cfe
use faker with a seed to get consistent data
adguernier Aug 14, 2023
b6b406f
add some basics tests
adguernier Aug 14, 2023
bc5536b
mock scrollTo function to prevent error in test
adguernier Aug 14, 2023
1a56ca7
fix a typo
adguernier Aug 16, 2023
a41a335
remove inner styled component to style list with class. Also remove s…
adguernier Aug 16, 2023
7559c28
fix a typo
adguernier Aug 16, 2023
bb4435b
use components instead of arrays of components
adguernier Aug 16, 2023
38a26c2
remove custom customers and filter on other city
adguernier Aug 16, 2023
a24756f
remove console.log
adguernier Aug 16, 2023
35f7c75
reorder conditions: if no record return null first
adguernier Aug 16, 2023
32b7c7e
always set limit to prevent no limit
adguernier Aug 16, 2023
e0b9922
rename test regardless of the data
adguernier Aug 16, 2023
8a6bb98
split listParams into more familiar sort and filter props
adguernier Aug 16, 2023
643bee7
fix tests based on test data
adguernier Aug 16, 2023
d628454
split prev and next button into components
adguernier Aug 16, 2023
af4c9f9
add a story to display error state
adguernier Aug 16, 2023
c2c3aa3
remove whole app to desmontrate only sx on stories
adguernier Aug 16, 2023
1943ca4
test WithFilter
adguernier Aug 16, 2023
dc97da0
add a WithLimit test
adguernier Aug 16, 2023
5663e92
format file
adguernier Aug 16, 2023
8914c72
rename PrevNextButton component in PrevNextButtons
adguernier Aug 16, 2023
597d28a
remove duplicated PrevNextButtons
adguernier Aug 16, 2023
be084c8
test it should go on edit / show view depends on linkType prop
adguernier Aug 16, 2023
6f4703f
test ErrorState
adguernier Aug 16, 2023
d647e9f
add some JS doc with description and examples
adguernier Aug 16, 2023
3f8f851
add a queryOptions prop to allow customizing it
adguernier Aug 17, 2023
a692f08
move PrevNextButtons logic in a dedicated hook controller
adguernier Aug 17, 2023
53aa1c9
add caption in JS Doc
adguernier Aug 17, 2023
e6f5f7c
add some JS Doc
adguernier Aug 17, 2023
270b194
reorder import and fix PrevNextButtonProps interface
adguernier Aug 17, 2023
efea314
fix interface
adguernier Aug 17, 2023
2e7ef6d
add a test for query filter
adguernier Aug 17, 2023
bd0e089
redirect to false in edit view
adguernier Aug 17, 2023
f65b08c
remove unused import
adguernier Aug 17, 2023
9b50e6d
add warnWhenUnsavedChanges in form
adguernier Aug 17, 2023
ef3bedf
linkType to show in show view
adguernier Aug 17, 2023
25571d2
[no-ci] Doc: add PrevNextButtons doc
adguernier Aug 17, 2023
8493597
[no-ci] Doc: add navigation to PrevNextButtons
adguernier Aug 17, 2023
172e3ac
[no-ci] WIP - Doc: add doc for usePrevNextController
adguernier Aug 17, 2023
24dc184
replace screencasts
adguernier Aug 18, 2023
8fd9595
[no-ci] Doc: fix after reviews in PrevNextButtons
adguernier Aug 18, 2023
2fe164c
[no-ci] Doc: remove usePrevNextController doc after reviews
adguernier Aug 18, 2023
d8a6d4e
change CircularProgress for LinearProgress and test it
adguernier Aug 18, 2023
6bb4674
rename tests after reviews and remove unused import
adguernier Aug 18, 2023
cebade3
use ListGuesser instead of a custom list
adguernier Aug 18, 2023
ca1cdf5
fix JS Dox after reviews
adguernier Aug 18, 2023
e1b1e19
fix JS Dox after reviews
adguernier Aug 18, 2023
4ae0d9c
rename navigateToPrev and navigateToNext to prevPath and nextPath
adguernier Aug 18, 2023
9d6721e
return null instead of throwing an error
adguernier Aug 18, 2023
6bb017b
indent sort object
adguernier Aug 18, 2023
d1b33b3
re-order merge of sort object
adguernier Aug 18, 2023
e6a00c8
update screencasts
adguernier Aug 18, 2023
ffd1b39
add a mention to prevnextbuttons component in Edit and Show chapters,
adguernier Aug 18, 2023
d86a6e0
fix code snippets
adguernier Aug 18, 2023
ca30a50
fix typo
adguernier Aug 18, 2023
9037633
fix merge of sort object
adguernier Aug 18, 2023
83ba583
Misc adjustments
fzaninotto Aug 21, 2023
fc1c65f
Remove useless clone
fzaninotto Aug 21, 2023
5189688
Update PrevNextButtons.md
fzaninotto Aug 21, 2023
2174493
Do not use permanent filter to initialize the store
fzaninotto Aug 21, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions docs/Edit.md
Original file line number Diff line number Diff line change
Expand Up @@ -745,3 +745,31 @@ 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.

## Navigating Through Records

[`<PrevNextButtons`](./PrevNextButtons.md) renders a navigation with two buttons, allowing users to navigate through records without leaving an `<Edit>` view.

<video controls autoplay playsinline muted loop>
<source src="./img/prev-next-buttons-edit.webm" type="video/webm" />
<source src="./img/prev-next-buttons-edit.mp4" type="video/mp4" />
Your browser does not support the video tag.
</video>

The following code is an example of how you can use it:

```tsx
export const PostEdit = () => (
<Edit
actions={
<TopToolbar>
<PrevNextButtons />
</TopToolbar>
}
>
...
</Edit>
);
```

**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-inedit-views-after-submit).
341 changes: 341 additions & 0 deletions docs/PrevNextButtons.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,341 @@
---
layout: default
title: "The PrevNextButtons Component"
---

# `<PrevNextButtons>`

The `<PrevNextButtons>` component renders navigation buttons linking to the next or previous record of a resource. It also renders the current index and the total number of records.

<video controls autoplay playsinline muted loop>
<source src="./img/prev-next-buttons.webm" type="video/webm" />
<source src="./img/prev-next-buttons.mp4" type="video/mp4" />
Your browser does not support the video tag.
</video>

`<PrevNextButtons>` can be used anywhere a [`RecordContext`](./Architecture.md#context-pull-dont-push) is provided (e.g. in an [Edit](./Edit.md) or [Show](./Show.md) view).

## Usage

```tsx
// in src/CustomerEdit.tsx
import { Edit, PrevNextButtons, ShowButton, SimpleForm, TextInput, TopToolbar } from 'react-admin';

export const CustomerEdit = () => (
<Edit
actions={
<TopToolbar>
<PrevNextButtons />
<ShowButton />
</TopToolbar>
}
>
<SimpleForm>
<TextInput source="first_name" />
<TextInput source="last_name" />
<TextInput source="email" />
<TextInput source="city" />
</SimpleForm>
</Edit>
);
```

## Props

| Prop | Required | Type | Default | Description |
| -------------- | -------- | ---------------- | ----------------------------------- | ------------------------------------------------------------------------------------------- |
| `filter` | Optional | `object` | `{}` | The permanent filter values. |
| `limit` | Optional | `number` | `1000` | Maximum number of records to fetch. |
| `linkType` | Optional | `string` | 'edit' | Specifies the view to redirect to when navigating. |
| `queryOptions` | Optional | `object` | `{ staleTime: 5 * 60 * 1000 }` | The options to pass to the useQuery hook. |
| `resource` | Optional | `string` | - | The resource name, e.g. `customers`. |
| `sort` | Optional | `object` | `{ field: 'id', order: SORT_ASC } ` | The sort parameters. |
| `storeKey` | Optional | `string | false` | - | The key to use to match a filter & sort configuration of a `<List>`. Pass false to disable. |
| `sx` | Optional | `object` | - | The CSS styles to apply to the component. |

## `filter`

Just like [Permanent `filter` in `<List>`](./List.md#filter-permanent-filter), you can specify what filter when fetching the list of records.

{% raw %}
```jsx
export const CustomerEdit = () => (
<Edit
actions={
<TopToolbar>
<PrevNextButtons filter={{ city: 'Hill Valley' }} />
</TopToolbar>
}
>
...
</Edit>
);
```
{% endraw %}

For example, this prop is useful to set the same `filter` as the `<List>` for the same resource:

{% raw %}
```tsx
export const MyAdmin = () => (
<Admin>
<Resource
name="customers"
list={
<List filter={{ city: 'Hill Valley' }}>
...
</List>
}
edit={
<Edit
actions={
<TopToolbar>
<PrevNextButtons filter={{ city: 'Hill Valley' }} />
</TopToolbar>
}
>
...
</Edit>
}
/>
</Admin>
);
```
{% endraw %}

## `limit`

You can set the maximum number of records to fetch with the `limit` prop. By default, `usePrevNextController` fetches a maximum of `1000` records.

```jsx
export const CustomerEdit = () => (
<Edit
actions={
<TopToolbar>
<PrevNextButtons limit={500}/>
</TopToolbar>
}
>
...
</Edit>
);
```

## `linkType`

By default `<PrevNextButtons>` items link to the `<Edit>` view. You can also set the `linkType` prop to `show` to link to the `<Show>` view instead.


```tsx
export const CustomerShow = () => (
<Show
actions={
<TopToolbar>
<PrevNextButtons linkType="show" />
</TopToolbar>
}
>
...
</Show>
);
```

`linkType` accepts the following values:

* `linkType="edit"`: links to the edit page. This is the default behavior.
* `linkType="show"`: links to the show page.

## `queryOptions`

`<PrevNextButtons>` accepts a `queryOptions` prop to pass options to the react-query client.

This can be useful e.g. to pass [a custom `meta`](./Actions.md#meta-parameter) to the `dataProvider.getList()` call.

{% raw %}
```tsx
export const CustomerShow = () => (
<Show
actions={
<TopToolbar>
<PrevNextButtons linkType="show" queryOptions={{ meta: { foo: 'bar' } }} />
</TopToolbar>
}
>
...
</Show>
);
```
{% endraw %}

## `resource`

By default, `<PrevNextButtons>` operates on the current `ResourceContext` (defined at the routing level), so under the `/customers` path, the `resource` prop will be `customers`. Pass a custom `resource` prop to override the `ResourceContext` value.

```jsx
export const CustomerEdit = () => (
<Edit
actions={
<TopToolbar>
<PrevNextButtons resource="users"/>
</TopToolbar>
}
>
...
</Edit>
);
```

## `sort`

Pass an object literal as the `sort` prop to set the `field` and `order` used for sorting:

{% raw %}
```jsx
export const CustomerEdit = () => (
<Edit
actions={
<TopToolbar>
<PrevNextButtons sort={{
field: 'first_name',
order: 'DESC',
}}/>
</TopToolbar>
}
>
...
</Edit>
);
```
{% endraw %}

For example, this prop is useful to set the same `filter` as the `<List>` view which handle the same resource:

{% raw %}
```tsx
export const MyAdmin = () => (
<Admin>
<Resource
name="customers"
list={
<List sort={{
field: 'first_name',
order: 'DESC',
}}>
...
</List>
}
edit={
<Edit
actions={
<TopToolbar>
<PrevNextButtons sort={{
field: 'first_name',
order: 'DESC',
}} />
</TopToolbar>
}
>
...
</Edit>
}
/>
</Admin>
);
```
{% endraw %}

## `storeKey`

`<PrevNextButtons>` can get the current list parameters (sort and filters) from the store.
This prop is useful if you specified a custom `storeKey` for a `<List>` and you want `<PrevNextButtons>` to use the same stored parameters.

See [`storeKey` in `<List>`](./List.md#storekey) for more informations.

```tsx
export const MyAdmin = () => (
<Admin>
<Resource
name="customers"
list={
<List storeKey="customers_key">
...
</List>
}
edit={
<Edit
actions={
<TopToolbar>
<PrevNextButtons storeKey="customers_key" />
</TopToolbar>
}
>
...
</Edit>
}
/>
</Admin>
);
```

## `sx`

The `<PrevNextButtons>` component accepts the usual `className` prop but you can override many class names injected to the inner components by React-admin thanks to the `sx` property (as most Material UI components, see their [documentation about it](https://mui.com/material-ui/customization/how-to-customize/#overriding-nested-component-styles)). This property accepts the following subclasse:

| Rule name | Description |
| -------------------------- | -------------------------------- |
| `& .RaPrevNextButton-list` | Applied to the list container |

Here is an example:

{% raw %}
```tsx
export const CustomerShow = () => (
<Show
actions={
<TopToolbar>
<PrevNextButtons
linkType="show"
sx={{
color: 'blue',
'& .RaPrevNextButton-list': {
padding: '10px',
},
}}
/>
</TopToolbar>
}
>
...
</Show>
);
```
{% endraw %}

## Navigating Through Records In`<Edit>` Views After Submit

Let's says users want to edit customer records and to navigate between records in the `<Edit>` view. The default react-admin behaviors causes two problems:
- when they save a record the user is redirected to the `<List>` view,
- when they navigate to another record, the form is not saved.

Thanks to React-admin components, you can solve these issues by using
- [`redirect` prop from `<Edit>`](Edit.md#redirect) with which you can specify the redirect to apply. Here we will choose to stay on the page rather than being redirected to the list view.
- [`warnWhenUnsavedChanges` from `Form`](Form.md#warnwhenunsavedchanges) that will triggers an alert if the user tries to change page while the record has not been saved.

{% raw %}
```tsx
export const CustomerEdit = () => (
<Edit
redirect={false}
actions={
<TopToolbar>
<PrevNextButtons />
</TopToolbar>
}
>
<SimpleForm warnWhenUnsavedChanges>
...
</SimpleForm>
</Edit>
);
```
{% endraw %}
Loading
Loading