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

Update refresh strategy to avoid empty page while refreshing #6054

Merged
merged 2 commits into from
Mar 26, 2021
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 packages/ra-core/src/actions/uiActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,12 @@ export const REFRESH_VIEW = 'RA/REFRESH_VIEW';

export interface RefreshViewAction {
readonly type: typeof REFRESH_VIEW;
readonly payload: { hard: boolean };
}

export const refreshView = (): RefreshViewAction => ({
export const refreshView = (hard?: boolean): RefreshViewAction => ({
type: REFRESH_VIEW,
payload: { hard },
});

export const SET_AUTOMATIC_REFRESH = 'RA/SET_AUTOMATIC_REFRESH';
Expand Down
36 changes: 36 additions & 0 deletions packages/ra-core/src/dataProvider/useDataProvider.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -640,6 +640,42 @@ describe('useDataProvider', () => {
expect(getOne).toBeCalledTimes(2);
});

it('should not use the cache after a hard refresh', async () => {
const getOne = jest.fn(() => {
const validUntil = new Date();
validUntil.setTime(validUntil.getTime() + 1000);
return Promise.resolve({ data: { id: 1 }, validUntil });
});
const dataProvider = { getOne };
const Refresh = () => {
const refresh = useRefresh();
return <button onClick={() => refresh(true)}>refresh</button>;
};
const { getByText, rerender } = renderWithRedux(
<DataProviderContext.Provider value={dataProvider}>
<UseGetOne key="1" />
<Refresh />
</DataProviderContext.Provider>,
{ admin: { resources: { posts: { data: {}, list: {} } } } }
);
// waitFor for the dataProvider to return
await act(async () => await new Promise(r => setTimeout(r)));
// click on the refresh button
expect(getOne).toBeCalledTimes(1);
await act(async () => {
fireEvent.click(getByText('refresh'));
await new Promise(r => setTimeout(r));
});
rerender(
<DataProviderContext.Provider value={dataProvider}>
<UseGetOne key="2" />
</DataProviderContext.Provider>
);
// waitFor for the dataProvider to return
await act(async () => await new Promise(r => setTimeout(r)));
expect(getOne).toBeCalledTimes(2);
});

it('should not use the cache after an update', async () => {
const getOne = jest.fn(() => {
const validUntil = new Date();
Expand Down
16 changes: 14 additions & 2 deletions packages/ra-core/src/reducer/admin/resource/list/cachedRequests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,20 @@ const cachedRequestsReducer: Reducer<State> = (
action
) => {
if (action.type === REFRESH_VIEW) {
// force refresh
return initialState;
if (action.payload?.hard) {
// force refresh
return initialState;
} else {
// remove validity only
const newState = {};
Object.keys(previousState).forEach(key => {
newState[key] = {
...previousState[key],
validity: undefined,
};
});
return newState;
}
}
if (action.meta && action.meta.optimistic) {
if (
Expand Down
14 changes: 13 additions & 1 deletion packages/ra-core/src/sideEffect/useRefresh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,27 @@ import { refreshView } from '../actions/uiActions';
/**
* Hook for Refresh Side Effect
*
* Returns a callback that triggers a page refresh. The callback causes a
* version increase, which forces a re-execution all queries based on the
* useDataProvider() hook, and a rerender of all components using the version
* as key.
*
* @param hard If true, the callback empties the cache, too
*
* @example
*
* const refresh = useRefresh();
* // soft refresh
* refresh();
* // hard refresh
* refresh(true)
*/
const useRefresh = () => {
const dispatch = useDispatch();
return useCallback(
(doRefresh = true) => doRefresh && dispatch(refreshView()),
Copy link
Member Author

Choose a reason for hiding this comment

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

this doRefresh parameter was undocumented and unused. Also, it's useless - one can replace it easily:

-refresh(doRefresh);
+doRefresh && refresh()

(hard?: boolean) => {
dispatch(refreshView(hard));
},
[dispatch]
);
};
Expand Down