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

Allow disabling store persistence of the list parameters #9019

Merged
merged 3 commits into from
Jun 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 3 additions & 1 deletion docs/List.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ You can find more advanced examples of `<List>` usage in the [demos](./Demos.md)
| `queryOptions` | Optional | `object` | - | The options to pass to the `useQuery` hook. |
| `resource` | Optional | `string` | - | The resource name, e.g. `posts`. |
| `sort` | Optional | `object` | - | The initial sort parameters. |
| `storeKey` | Optional | `string` | - | The key to use to store the current filter & sort. |
| `storeKey` | Optional | `string` | `false` | - | The key to use to store the current filter & sort. Pass `false` to disable |
| `title` | Optional | `string` | - | The title to display in the App Bar. |
| `sx` | Optional | `object` | - | The CSS styles to apply to the component. |

Expand Down Expand Up @@ -849,6 +849,8 @@ const Admin = () => {

**Tip:** The `storeKey` is actually passed to the underlying `useListController` hook, which you can use directly for more complex scenarios. See the [`useListController` doc](./useListController.md#storekey) for more info.

You can disable this feature by setting the `storeKey` prop to `false`. When disabled, parameters will not be persisted in the store.

## `title`

The default title for a list view is "[resource] list" (e.g. "Posts list"). Use the `title` prop to customize the List view title:
Expand Down
3 changes: 3 additions & 0 deletions docs/useListController.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,9 @@ const FlopPosts = (
```
{% endraw %}

You can disable this feature by setting the `storeKey` prop to `false`. When disabled, parameters will not be persisted in the store.


## Return Value

The return value of `useListController` has the following shape:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ export interface InfiniteListControllerProps<
>;
resource?: string;
sort?: SortPayload;
storeKey?: string;
storeKey?: string | false;
}

export interface InfiniteListControllerResult<RecordType extends RaRecord = any>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,13 @@ import {
act,
} from '@testing-library/react';
import { createMemoryHistory } from 'history';
import { ListsUsingSameResource } from './useListController.storeKey.stories';
import {
ListsUsingSameResource,
ListsWithoutStore,
} from './useListController.storeKey.stories';

describe('useListController', () => {
describe('customStoreKey', () => {
describe('storeKey', () => {
it('should keep distinct two lists of the same resource given different keys', async () => {
render(
<ListsUsingSameResource
Expand Down Expand Up @@ -43,5 +46,67 @@ describe('useListController', () => {
screen.getByLabelText('perPage').getAttribute('data-value')
).toEqual('3');
});

it('should not use the store when storeKey is false', async () => {
render(
<ListsWithoutStore
history={createMemoryHistory({
initialEntries: ['/store'],
})}
/>
);

await waitFor(() => {
expect(
screen.getByLabelText('perPage').getAttribute('data-value')
).toEqual('3');
});

act(() => {
fireEvent.click(screen.getByLabelText('incrementPerPage'));
fireEvent.click(screen.getByLabelText('incrementPerPage'));
});

await waitFor(() => {
expect(
screen.getByLabelText('perPage').getAttribute('data-value')
).toEqual('5');
});

act(() => {
fireEvent.click(screen.getByLabelText('nostore'));
});
expect(
screen.getByLabelText('perPage').getAttribute('data-value')
).toEqual('3');

act(() => {
fireEvent.click(screen.getByLabelText('incrementPerPage'));
});

await waitFor(() => {
expect(
screen.getByLabelText('perPage').getAttribute('data-value')
).toEqual('4');
});

act(() => {
fireEvent.click(screen.getByLabelText('store'));
});
// Shouldn't have changed the store list
await waitFor(() => {
expect(
screen.getByLabelText('perPage').getAttribute('data-value')
).toEqual('5');
});

act(() => {
fireEvent.click(screen.getByLabelText('nostore'));
});
// Should have reset its parameters to their default
expect(
screen.getByLabelText('perPage').getAttribute('data-value')
).toEqual('3');
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ const OrderedPostList = ({
storeKey,
sort,
}: {
storeKey: string;
storeKey: string | false;
sort?: SortPayload;
}) => {
const params = useListController({
Expand Down Expand Up @@ -117,6 +117,12 @@ const TopPosts = (
const FlopPosts = (
<OrderedPostList storeKey="flop" sort={{ field: 'votes', order: 'ASC' }} />
);
const StorePosts = (
<OrderedPostList storeKey="store" sort={{ field: 'votes', order: 'ASC' }} />
);
const NoStorePosts = (
<OrderedPostList storeKey={false} sort={{ field: 'votes', order: 'ASC' }} />
);

export const ListsUsingSameResource = (argsOrProps, context) => {
const history = context?.history || argsOrProps.history;
Expand All @@ -138,3 +144,39 @@ export const ListsUsingSameResource = (argsOrProps, context) => {
</CoreAdminContext>
);
};

const NoStoreLayout = (props: CoreLayoutProps) => {
return (
<div style={styles.mainContainer}>
<Link aria-label="store" to={`/store`}>
Go to Store List
</Link>{' '}
<Link aria-label="nostore" to={`/nostore`}>
Go to No Store List
</Link>
<br />
<br />
{props.children}
</div>
);
};
export const ListsWithoutStore = (argsOrProps, context) => {
const history = context?.history || argsOrProps.history;
return (
<CoreAdminContext
history={history}
store={localStorageStore()}
dataProvider={dataProvider}
>
<CoreAdminUI layout={NoStoreLayout}>
<CustomRoutes>
<Route path="/store" element={StorePosts} />
</CustomRoutes>
<CustomRoutes>
<Route path="/nostore" element={NoStorePosts} />
</CustomRoutes>
<Resource name="posts" />
</CoreAdminUI>
</CoreAdminContext>
);
};
2 changes: 1 addition & 1 deletion packages/ra-core/src/controller/list/useListController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ export interface ListControllerProps<RecordType extends RaRecord = any> {
}> & { meta?: any };
resource?: string;
sort?: SortPayload;
storeKey?: string;
storeKey?: string | false;
}

const defaultSort = {
Expand Down
26 changes: 21 additions & 5 deletions packages/ra-core/src/controller/list/useListParams.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,15 +88,25 @@ export const useListParams = ({
const location = useLocation();
const navigate = useNavigate();
const [localParams, setLocalParams] = useState(defaultParams);
const [params, setParams] = useStore(storeKey, defaultParams);
// As we can't conditionally call a hook, if the storeKey is false,
// we'll ignore the params variable later on and won't call setParams either.
const [params, setParams] = useStore(
storeKey || `${resource}.listParams`,
Copy link
Member

Choose a reason for hiding this comment

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

if storeKey is false, this will still use the store. Can you add a comment to explain what's the rationale?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

We can't disable a hook, so although we still read the store, its values are not used later in the code

defaultParams
);
const tempParams = useRef<ListParams>();
const isMounted = useIsMounted();
const disableSyncWithStore = storeKey === false;

const requestSignature = [
location.search,
resource,
storeKey,
JSON.stringify(disableSyncWithLocation ? localParams : params),
JSON.stringify(
disableSyncWithLocation || disableSyncWithStore
? localParams
: params
),
JSON.stringify(filterDefaultValues),
JSON.stringify(sort),
perPage,
Expand All @@ -111,7 +121,10 @@ export const useListParams = ({
() =>
getQuery({
queryFromLocation,
params: disableSyncWithLocation ? localParams : params,
params:
disableSyncWithLocation || disableSyncWithStore
Copy link
Member

Choose a reason for hiding this comment

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

I think you must also dd the check for disableSyncWithStore in changeParams L146, and anywhere else the setParams may be called.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

changeParams does not call setParams

? localParams
: params,
filterDefaultValues,
sort,
perPage,
Expand All @@ -124,7 +137,10 @@ export const useListParams = ({
// store as well so that we don't lose them after a redirection back
// to the list
useEffect(() => {
if (Object.keys(queryFromLocation).length > 0) {
if (
Object.keys(queryFromLocation).length > 0 &&
!disableSyncWithStore
) {
setParams(query);
}
}, [location.search]); // eslint-disable-line
Expand Down Expand Up @@ -377,7 +393,7 @@ export interface ListParamsOptions {
perPage?: number;
resource: string;
sort?: SortPayload;
storeKey?: string;
storeKey?: string | false;
}

interface Parameters extends ListParams {
Expand Down
70 changes: 70 additions & 0 deletions packages/ra-ui-materialui/src/list/List.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,76 @@ export const StoreKey = () => {
);
};

const BooksWithStoreEnabled = () => (
<List
resource="books"
storeKey="booksStore"
sort={{ field: 'year', order: 'DESC' }}
>
<Datagrid>
<TextField source="id" />
<TextField source="title" />
<TextField source="author" />
<TextField source="year" />
</Datagrid>
</List>
);

const BooksWithStoreDisabled = () => (
<List
resource="books"
storeKey={false}
sort={{ field: 'year', order: 'ASC' }}
>
<Datagrid>
<TextField source="id" />
<TextField source="title" />
<TextField source="author" />
<TextField source="year" />
</Datagrid>
</List>
);

const DisabledStoreDashboard = () => (
<>
<Box>
<Button
component={Link}
sx={{ margin: 2 }}
to="/store"
variant="contained"
>
See books with store enabled
</Button>
<Button
component={Link}
sx={{ margin: 2 }}
to="/nostore"
variant="contained"
>
See books with store disabled
</Button>
</Box>
</>
);

export const StoreDisabled = () => {
history.push('/');
return (
<Admin
dataProvider={dataProvider}
history={history}
dashboard={DisabledStoreDashboard}
>
<CustomRoutes>
<Route path="/store" element={<BooksWithStoreEnabled />} />
<Route path="/nostore" element={<BooksWithStoreDisabled />} />
</CustomRoutes>
<Resource name="books" />
</Admin>
);
};

export const ErrorInFetch = () => (
<Admin
dataProvider={
Expand Down