marmelab/react-admin · error · Error

The dataProvider threw an error. It should return a rejected

Error message

The dataProvider threw an error. It should return a rejected Promise instead.

What it means

Data providers must communicate failures by returning a rejected Promise, not by throwing synchronously. When the proxied call throws synchronously, useDataProvider catches it, logs it in development, and rethrows this canonical error so the failure semantics stay uniform.

Source

Thrown at packages/ra-core/src/dataProvider/useDataProvider.ts:151

                                return logoutIfAccessDenied(error).then(
                                    loggedOut => {
                                        if (loggedOut)
                                            return {
                                                data: arrayReturnTypes.includes(
                                                    type
                                                )
                                                    ? []
                                                    : {},
                                            };
                                        throw error;
                                    }
                                );
                            });
                    } catch (e) {
                        if (process.env.NODE_ENV !== 'production') {
                            console.error(e);
                        }
                        throw new Error(
                            'The dataProvider threw an error. It should return a rejected Promise instead.'
                        );
                    }
                };
            },
        });
    }, [dataProvider, logoutIfAccessDenied, queryClient]);

    return dataProviderProxy;
};

const isAbortError = error =>
    error instanceof DOMException &&
    (error as DOMException).name === 'AbortError';

View on GitHub (pinned to 051f511bb0)

Solutions

  1. Make provider methods async so thrown errors become rejections
  2. Replace `throw e` with `return Promise.reject(e)` in non-async methods
  3. Wrap the method body in try/catch and rethrow inside async context

Example fix

// before
getOne: (resource, params) => {
  if (!params.id) throw new Error('no id');
  return fetch(...);
},
// after
getOne: async (resource, params) => {
  if (!params.id) throw new Error('no id'); // async: becomes rejection
  return fetch(...);
},
Defensive patterns

Strategy: try-catch

Validate before calling

const rejects = async (fn: (...a: any[]) => any) => {
  try { await fn(); return false; } catch { return true; }
};
// in provider tests: assert(await rejects(() => dp.getOne('x', { id: null })))

Type guard

const returnsPromise = (fn: unknown): fn is (...args: any[]) => Promise<unknown> =>
  typeof fn === 'function';
// and always call via async so throws become rejections

Try / catch

// inside the provider, never throw synchronously
getOne: async (resource, params) => {
  try {
    return await fetchOne(resource, params);
  } catch (e) {
    throw e; // async => rejection, satisfies the contract
  }
}

Prevention

When it happens

Trigger: A dataProvider method implementation using `throw new Error(...)` instead of `return Promise.reject(...)` or an async function that rejects; synchronous validation inside a non-async method.

Common situations: Custom data providers written with plain function syntax; mocks that throw synchronously in tests; auth-failure paths that throw before returning a promise.

Related errors


AI-assisted analysis of marmelab/react-admin@051f511bb0 (2026-08-30). Data as JSON: /api/errors/64f6eb9181eb7fb5. Report an issue: GitHub.