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

Fix crash when navigating away during undo period #5487

Merged
merged 2 commits into from
Nov 5, 2020
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
35 changes: 32 additions & 3 deletions packages/ra-core/src/dataProvider/useDataProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { getDataProviderCallArguments } from './getDataProviderCallArguments';
// These calls get replayed once the dataProvider exits optimistic mode
const optimisticCalls = [];
const undoableOptimisticCalls = [];
let nbRemainingOptimisticCalls = 0;

/**
* Hook for getting a dataProvider
Expand Down Expand Up @@ -190,7 +191,13 @@ const useDataProvider = (): DataProviderProxy => {
} else {
optimisticCalls.push(params);
}
return Promise.resolve();
nbRemainingOptimisticCalls++;
// Return a Promise that only resolves when the optimistic call was made
// otherwise hooks like useQueryWithStore will return loaded = true
// before the content actually reaches the Redux store.
// But as we can't determine when this particular query was finished,
// the Promise resolves only when *all* optimistic queries are done.
return waitFor(() => nbRemainingOptimisticCalls === 0);
}
return doQuery(params);
};
Expand All @@ -201,6 +208,18 @@ const useDataProvider = (): DataProviderProxy => {
return dataProviderProxy;
};

// get a Promise that resolves after a delay in milliseconds
const later = (delay = 100): Promise<void> =>
new Promise(function (resolve) {
setTimeout(resolve, delay);
});

// get a Promise that resolves once a condition is satisfied
const waitFor = (condition: () => boolean): Promise<void> =>
new Promise(resolve =>
condition() ? resolve() : later().then(() => waitFor(condition))
);

const doQuery = ({
type,
payload,
Expand Down Expand Up @@ -279,7 +298,7 @@ const performUndoableQuery = ({
dispatch,
logoutIfAccessDenied,
allArguments,
}: QueryFunctionParams) => {
}: QueryFunctionParams): Promise<{}> => {
dispatch(startOptimisticMode());
if (window) {
window.addEventListener('beforeunload', warnBeforeClosingWindow, {
Expand Down Expand Up @@ -419,18 +438,28 @@ const replayOptimisticCalls = async () => {
// We only handle all side effects queries if there are no more undoable queries
if (undoableOptimisticCalls.length > 0) {
clone = [...undoableOptimisticCalls];
// remove these calls from the list *before* doing them
// because side effects in the calls can add more calls
// so we don't want to erase these.
undoableOptimisticCalls.splice(0, undoableOptimisticCalls.length);

await Promise.all(
clone.map(params => Promise.resolve(doQuery.call(null, params)))
);
// once the calls are finished, decrease the number of remaining calls
nbRemainingOptimisticCalls -= clone.length;
} else {
clone = [...optimisticCalls];
// remove these calls from the list *before* doing them
// because side effects in the calls can add more calls
// so we don't want to erase these.
optimisticCalls.splice(0, optimisticCalls.length);

await Promise.all(
clone.map(params => Promise.resolve(doQuery.call(null, params)))
);
// once the calls are finished, decrease the number of remaining calls
nbRemainingOptimisticCalls -= clone.length;
}
};

Expand All @@ -452,7 +481,7 @@ const performQuery = ({
dispatch,
logoutIfAccessDenied,
allArguments,
}: QueryFunctionParams) => {
}: QueryFunctionParams): Promise<any> => {
dispatch({
type: action,
payload,
Expand Down