Search code examples
react-admin

Can you share your dataProvider with client-side sorting and filtering?


I have a basic REST API I'm using (no queries, no pagination) and I have it integrated very nicely into the React Admin, with edit, show, and create actions. All very nice. However, my API does not have any filtering or sorting so the power of the Datagrid is lost because I can't search or sort the columns.

I know I need to implement client-side functions to filter and sort the API myself within a custom dataProvider. Instead of building this all from scratch, can anyone share their custom dataProvider with me that already has local sort, filter, etc. that I can adapt instead of building from scratch?

Alternatively, I implemented Tubular for React last night and I loved its ease-of-use but it lacks good integration with React Admin. Has anyone implemented Tubular within React Admin and how did you approach it?

Thank you in advance for any assistance!!


Solution

  • Here is dataProvider I have written for our project. All filtering, sorting, pagination were done on the client side.

    import { fetchUtils, DataProvider } from 'ra-core';
    
    const dataProvider = (apiUrl : string, httpClient = fetchUtils.fetchJson) : DataProvider => ({
        getList: async (resource, params) => {
            const { json } = await httpClient(`${ apiUrl }/${ resource }`);
            const feedFilter = params.filter["feed"];
            const stateFilter = params.filter["state"];
    
            const result = json
                .filter(e => {
                    if (!feedFilter) {
                        return true;
                    }
                    return e.feed.includes(feedFilter.toUpperCase());
                })
                .filter(e => {
                    if (!stateFilter) {
                        return true;
                    }
                    return e.state === stateFilter;
                });
    
            const { field, order } = params.sort;
            result.sort(dynamicSort(field, order));
    
            const { page, perPage } = params.pagination;
            const showedResult = result.slice((page - 1) * perPage, page * perPage);
    
            return {
                data: showedResult.map((resource : { feed : string; }) => ({ ...resource, id: resource.feed })),
                total: result.length,
            };
        },
    
        getMany: async (resource) => {
            const url = `${ apiUrl }/${ resource }`;
            const { json } = await httpClient(url);
            return ({
                data: json.map((resource : { feed : string; }) => ({ ...resource, id: resource.feed })),
            });
        },
    
        getManyReference: async (resource) => {
            const url = `${ apiUrl }/${ resource }`;
    
            const { headers, json } = await httpClient(url);
            return ({
                data: json.map((resource : { feed : string; }) => ({ ...resource, id: resource.feed })),
                total: parseInt(headers.get('X-Total-Count') || "", 10),
            });
        },
    
        getOne: (resource, params) =>
            httpClient(`${ apiUrl }/${ resource }/${ params.id }`).then(({ json }) => ({
                data: json,
            })),
    
        update: (resource, params) =>
            httpClient(`${ apiUrl }/${ resource }/${ params.id }`, {
                method: 'PUT',
                body: JSON.stringify(params.data),
            }).then(({ json }) => ({ data: json })),
    
        updateMany: async (resource, params) => {
            const { json } = await httpClient(`${ apiUrl }/${ resource }`, {
                method: 'PUT',
                body: JSON.stringify(params.data),
            });
            return ({ data: json });
        },
    
        create: (resource, params) =>
            httpClient(`${ apiUrl }/${ resource }`, {
                method: 'POST',
                body: JSON.stringify(params.data),
            }).then(({ json }) => ({
                data: { ...params.data, id: json.id },
            })),
    
        delete: (resource, params) =>
            httpClient(`${ apiUrl }/${ resource }/${ params.id }`, {
                method: 'DELETE',
            }).then(({ json }) => ({ data: json })),
    
        deleteMany: async (resource, params) => {
            const { json } = await httpClient(`${ apiUrl }/${ resource }`, {
                method: 'DELETE',
                body: JSON.stringify(params.ids),
            });
            return ({ data: json });
        },
    });
    
    function dynamicSort(property : string, order : string){
        let sortOrder = 1;
        if (order === "DESC") {
            sortOrder = -1;
        }
        return function (a : any, b : any){
            let aProp = a[property];
            let bProp = b[property];
            if (!a.hasOwnProperty(property)) {
                aProp = ''
            }
            if (!b.hasOwnProperty(property)) {
                bProp = ''
            }
            const result = (aProp < bProp) ? -1 : (aProp > bProp) ? 1 : 0;
            return result * sortOrder;
        }
    }
    
    const cacheDataProviderProxy = (dataProvider : any, duration = 5 * 60 * 1000) =>
        new Proxy(dataProvider, {
            get: (dataProvider, name) => (resource : string, params : any) => {
                if (name === 'getOne' || name === 'getMany' || name === 'getList') {
                    return dataProvider.name(resource, params).then((response : { validUntil : Date; }) => {
                        const validUntil = new Date();
                        validUntil.setTime(validUntil.getTime() + duration);
                        response.validUntil = validUntil;
                        return response;
                    });
                }
                return dataProvider.name(resource, params);
            },
        });
    
    export default cacheDataProviderProxy(dataProvider);