diff --git a/packages/ra-core/src/actions/uiActions.ts b/packages/ra-core/src/actions/uiActions.ts index 6c3e7356b3e..3868f19ecc5 100644 --- a/packages/ra-core/src/actions/uiActions.ts +++ b/packages/ra-core/src/actions/uiActions.ts @@ -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'; diff --git a/packages/ra-core/src/dataProvider/useDataProvider.spec.js b/packages/ra-core/src/dataProvider/useDataProvider.spec.js index a25010dca66..b4f4de7f233 100644 --- a/packages/ra-core/src/dataProvider/useDataProvider.spec.js +++ b/packages/ra-core/src/dataProvider/useDataProvider.spec.js @@ -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 ; + }; + const { getByText, rerender } = renderWithRedux( + + + + , + { 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( + + + + ); + // 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(); diff --git a/packages/ra-core/src/reducer/admin/resource/list/cachedRequests.ts b/packages/ra-core/src/reducer/admin/resource/list/cachedRequests.ts index 01b99efcb66..151d64b6072 100644 --- a/packages/ra-core/src/reducer/admin/resource/list/cachedRequests.ts +++ b/packages/ra-core/src/reducer/admin/resource/list/cachedRequests.ts @@ -32,8 +32,20 @@ const cachedRequestsReducer: Reducer = ( 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 ( diff --git a/packages/ra-core/src/sideEffect/useRefresh.ts b/packages/ra-core/src/sideEffect/useRefresh.ts index 14825aa52d4..b35499efbd7 100644 --- a/packages/ra-core/src/sideEffect/useRefresh.ts +++ b/packages/ra-core/src/sideEffect/useRefresh.ts @@ -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()), + (hard?: boolean) => { + dispatch(refreshView(hard)); + }, [dispatch] ); };