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]
);
};