diff --git a/examples/simple/src/users/UserEdit.js b/examples/simple/src/users/UserEdit.js
index 56c23578f74..9c1921ee7f5 100644
--- a/examples/simple/src/users/UserEdit.js
+++ b/examples/simple/src/users/UserEdit.js
@@ -14,6 +14,9 @@ import {
TextInput,
Toolbar,
TopToolbar,
+ ReferenceManyField,
+ SingleFieldList,
+ ChipField,
} from 'react-admin';
import { makeStyles } from '@material-ui/core/styles';
@@ -71,6 +74,15 @@ const UserEdit = ({ permissions, ...props }) => (
defaultValue="slim shady"
validate={required()}
/>
+
+
+
+
+
{permissions === 'admin' && (
diff --git a/packages/ra-core/src/dataProvider/useDataProvider.ts b/packages/ra-core/src/dataProvider/useDataProvider.ts
index da7eefaec20..649c264f91a 100644
--- a/packages/ra-core/src/dataProvider/useDataProvider.ts
+++ b/packages/ra-core/src/dataProvider/useDataProvider.ts
@@ -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
@@ -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);
};
@@ -201,6 +208,18 @@ const useDataProvider = (): DataProviderProxy => {
return dataProviderProxy;
};
+// get a Promise that resolves after a delay in milliseconds
+const later = (delay = 100): Promise =>
+ new Promise(function (resolve) {
+ setTimeout(resolve, delay);
+ });
+
+// get a Promise that resolves once a condition is satisfied
+const waitFor = (condition: () => boolean): Promise =>
+ new Promise(resolve =>
+ condition() ? resolve() : later().then(() => waitFor(condition))
+ );
+
const doQuery = ({
type,
payload,
@@ -279,7 +298,7 @@ const performUndoableQuery = ({
dispatch,
logoutIfAccessDenied,
allArguments,
-}: QueryFunctionParams) => {
+}: QueryFunctionParams): Promise<{}> => {
dispatch(startOptimisticMode());
if (window) {
window.addEventListener('beforeunload', warnBeforeClosingWindow, {
@@ -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;
}
};
@@ -452,7 +481,7 @@ const performQuery = ({
dispatch,
logoutIfAccessDenied,
allArguments,
-}: QueryFunctionParams) => {
+}: QueryFunctionParams): Promise => {
dispatch({
type: action,
payload,