Skip to content

Commit 4dd34fc

Browse files
committed
Improve lists URL management
1 parent 4200e46 commit 4dd34fc

File tree

2 files changed

+143
-0
lines changed

2 files changed

+143
-0
lines changed

packages/ra-core/src/controller/list/useListParams.spec.tsx

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { useStore } from '../../store/useStore';
99
import { useListParams, getQuery, getNumberOrDefault } from './useListParams';
1010
import { SORT_DESC, SORT_ASC } from './queryReducer';
1111
import { TestMemoryRouter } from '../../routing';
12+
import { memoryStore } from '../../store';
1213

1314
describe('useListParams', () => {
1415
describe('getQuery', () => {
@@ -495,6 +496,71 @@ describe('useListParams', () => {
495496
});
496497
});
497498

499+
it('should synchronize location with store when sync is enabled', async () => {
500+
let location;
501+
let storeValue;
502+
const StoreReader = () => {
503+
const [value] = useStore('posts.listParams');
504+
React.useEffect(() => {
505+
storeValue = value;
506+
}, [value]);
507+
return null;
508+
};
509+
render(
510+
<TestMemoryRouter
511+
locationCallback={l => {
512+
location = l;
513+
}}
514+
>
515+
<CoreAdminContext
516+
dataProvider={testDataProvider()}
517+
store={memoryStore({
518+
'posts.listParams': {
519+
sort: 'id',
520+
order: 'ASC',
521+
page: 10,
522+
perPage: 10,
523+
filter: {},
524+
},
525+
})}
526+
>
527+
<Component />
528+
<StoreReader />
529+
</CoreAdminContext>
530+
</TestMemoryRouter>
531+
);
532+
533+
await waitFor(() => {
534+
expect(storeValue).toEqual({
535+
sort: 'id',
536+
order: 'ASC',
537+
page: 10,
538+
perPage: 10,
539+
filter: {},
540+
});
541+
});
542+
543+
await waitFor(() => {
544+
expect(location).toEqual(
545+
expect.objectContaining({
546+
hash: '',
547+
key: expect.any(String),
548+
state: null,
549+
pathname: '/',
550+
search:
551+
'?' +
552+
stringify({
553+
filter: JSON.stringify({}),
554+
sort: 'id',
555+
order: 'ASC',
556+
page: 10,
557+
perPage: 10,
558+
}),
559+
})
560+
);
561+
});
562+
});
563+
498564
it('should not synchronize parameters with location and store when sync is not enabled', async () => {
499565
let location;
500566
let storeValue;
@@ -540,6 +606,63 @@ describe('useListParams', () => {
540606
expect(storeValue).toBeUndefined();
541607
});
542608

609+
it('should not synchronize location with store when sync is not enabled', async () => {
610+
let location;
611+
let storeValue;
612+
const StoreReader = () => {
613+
const [value] = useStore('posts.listParams');
614+
React.useEffect(() => {
615+
storeValue = value;
616+
}, [value]);
617+
return null;
618+
};
619+
render(
620+
<TestMemoryRouter
621+
locationCallback={l => {
622+
location = l;
623+
}}
624+
>
625+
<CoreAdminContext
626+
dataProvider={testDataProvider()}
627+
store={memoryStore({
628+
'posts.listParams': {
629+
sort: 'id',
630+
order: 'ASC',
631+
page: 10,
632+
perPage: 10,
633+
filter: {},
634+
},
635+
})}
636+
>
637+
<Component disableSyncWithLocation />
638+
<StoreReader />
639+
</CoreAdminContext>
640+
</TestMemoryRouter>
641+
);
642+
643+
await waitFor(() => {
644+
expect(storeValue).toEqual({
645+
sort: 'id',
646+
order: 'ASC',
647+
page: 10,
648+
perPage: 10,
649+
filter: {},
650+
});
651+
});
652+
653+
await waitFor(() => {
654+
expect(location).toEqual(
655+
expect.objectContaining({
656+
hash: '',
657+
key: expect.any(String),
658+
state: null,
659+
pathname: '/',
660+
search: '',
661+
})
662+
);
663+
});
664+
});
665+
543666
it('should synchronize parameters with store when sync is not enabled and storeKey is passed', async () => {
544667
let storeValue;
545668
const Component = ({

packages/ra-core/src/controller/list/useListParams.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { useCallback, useMemo, useEffect, useState, useRef } from 'react';
22
import { parse, stringify } from 'query-string';
33
import lodashDebounce from 'lodash/debounce.js';
4+
import isEqual from 'lodash/isEqual.js';
45
import { useNavigate, useLocation } from 'react-router-dom';
56

67
import { useStore } from '../../store';
@@ -133,6 +134,25 @@ export const useListParams = ({
133134
}
134135
}, [location.search]); // eslint-disable-line
135136

137+
// if the location includes params (for example from a link like
138+
// the categories products on the demo), we need to persist them in the
139+
// store as well so that we don't lose them after a redirection back
140+
// to the list
141+
useEffect(() => {
142+
if (disableSyncWithLocation) {
143+
return;
144+
}
145+
if (!isEqual(query, queryFromLocation)) {
146+
navigate({
147+
search: `?${stringify({
148+
...query,
149+
filter: JSON.stringify(query.filter),
150+
displayedFilters: JSON.stringify(query.displayedFilters),
151+
})}`,
152+
});
153+
}
154+
}, [disableSyncWithLocation, query, location.search]); // eslint-disable-line
155+
136156
const changeParams = useCallback(
137157
action => {
138158
// do not change params if the component is already unmounted

0 commit comments

Comments
 (0)