ra-core
Version:
Core components of react-admin, a frontend Framework for building admin applications on top of REST services, using ES6, React
166 lines (155 loc) • 6.25 kB
text/typescript
import { useContext, useMemo } from 'react';
import { useQueryClient } from '@tanstack/react-query';
import DataProviderContext from './DataProviderContext';
import { defaultDataProvider } from './defaultDataProvider';
import validateResponseFormat from './validateResponseFormat';
import { DataProvider } from '../types';
import useLogoutIfAccessDenied from '../auth/useLogoutIfAccessDenied';
import { reactAdminFetchActions } from './dataFetchActions';
import { populateQueryCache } from './populateQueryCache';
/**
* Hook for getting a dataProvider
*
* Gets a dataProvider object, which behaves just like the real dataProvider
* (same methods returning a Promise). But it's actually a Proxy object,
* which validates the response format, and logs the user out upon error
* if authProvider.checkError() rejects.
*
* @return dataProvider
*
* @example Basic usage
*
* import * as React from 'react';
* import { useState } from 'react';
* import { useDataProvider } from 'react-admin';
*
* const PostList = () => {
* const [posts, setPosts] = useState([])
* const dataProvider = useDataProvider();
* useEffect(() => {
* dataProvider.getList('posts', { filter: { status: 'pending' }})
* .then(({ data }) => setPosts(data));
* }, [])
*
* return (
* <Fragment>
* {posts.map((post, key) => <PostDetail post={post} key={key} />)}
* </Fragment>
* );
* }
*
* @example Handling all states (loading, error, success)
*
* import { useState, useEffect } from 'react';
* import { useDataProvider } from 'react-admin';
*
* const UserProfile = ({ userId }) => {
* const dataProvider = useDataProvider();
* const [user, setUser] = useState();
* const [loading, setLoading] = useState(true);
* const [error, setError] = useState();
* useEffect(() => {
* dataProvider.getOne('users', { id: userId })
* .then(({ data }) => {
* setUser(data);
* setLoading(false);
* })
* .catch(error => {
* setError(error);
* setLoading(false);
* })
* }, []);
*
* if (loading) return <Loading />;
* if (error) return <Error />
* if (!user) return null;
*
* return (
* <ul>
* <li>Name: {user.name}</li>
* <li>Email: {user.email}</li>
* </ul>
* )
* }
*/
const arrayReturnTypes = ['getList', 'getMany', 'getManyReference'];
export const useDataProvider = <
TDataProvider extends DataProvider = DataProvider,
>(): TDataProvider => {
const dataProvider = (useContext(DataProviderContext) ||
defaultDataProvider) as unknown as TDataProvider;
const queryClient = useQueryClient();
const logoutIfAccessDenied = useLogoutIfAccessDenied();
const dataProviderProxy = useMemo(() => {
return new Proxy(dataProvider, {
get: (_, name) => {
if (typeof name === 'symbol' || name === 'then') {
return;
}
if (name === 'supportAbortSignal') {
return dataProvider.supportAbortSignal;
}
return (...args) => {
const type = name.toString();
if (typeof dataProvider[type] !== 'function') {
throw new Error(
`Unknown dataProvider function: ${type}`
);
}
try {
return dataProvider[type]
.apply(dataProvider, args)
.then(response => {
if (
process.env.NODE_ENV === 'development' &&
reactAdminFetchActions.includes(type)
) {
validateResponseFormat(response, type);
}
if (response?.meta?.prefetched) {
populateQueryCache({
data: response?.meta.prefetched,
queryClient,
});
}
return response;
})
.catch(error => {
if (
process.env.NODE_ENV !== 'production' &&
// do not log AbortErrors
!isAbortError(error)
) {
console.error(error);
}
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';