8

I'm trying to use multiple dataproviders in a react-admin project but I have an error:

Warning: Missing translation for key: "dataProvider is not a function"
function.console.(anonymous function) @ index.js:1452

I have my App.js like this:

import React from 'react';
import { render } from 'react-dom';
import { Admin, Resource } from 'react-admin';
import dataProviders from './service/dataproviders';
import UserList1,  from './users1';
import UserList2,  from './users2';

const App = () => (
  render(
    <Admin dataProvider={dataProviders}>
      <Resource name="users1" list={UserList1} />
      <Resource name="users2" list={UserList2} />
    </Admin>,
    document.getElementById('root'),
  )
);

export default App;

And I have the dataproviders.js file:

import simpleRestProvider from 'ra-data-simple-rest';

const dataProviders = [
  { dataProvider: simpleRestProvider('http://path.to.foo.api1'), resources: ['users1'] },
  { dataProvider: simpleRestProvider('http://path.to.foo.api2'), resources: ['users2'] },
];

export default (type, resource, params) => {
  const dataProvider = dataProviders.find(dp => dp.resources.includes(resource));
  return dataProvider(type, resource, params);
};

How it's the correct way?

I check this post:

Is it possible to have multiple dataProviders in react-admin?

Javier C.
  • 7,859
  • 5
  • 41
  • 53
  • For version 3.0 and above, this will work: https://stackoverflow.com/a/66347998/17454933 – Jabr Jan 07 '22 at 13:24

3 Answers3

5

You're not calling the dataProvider you found, you're calling the mapping object from your array. You can fix it like this:

import simpleRestProvider from 'ra-data-simple-rest';

const dataProviders = [
  { dataProvider: simpleRestProvider('http://path.to.foo.api1'), resources: ['users1'] },
  { dataProvider: simpleRestProvider('http://path.to.foo.api2'), resources: ['users2'] },
];

export default (type, resource, params) => {
  const dataProviderMapping = dataProviders.find(dp => dp.resources.includes(resource));
  return dataProviderMapping.dataProvider(type, resource, params);
};
Gildas Garcia
  • 6,966
  • 3
  • 15
  • 29
  • 1
    I get "TypeError: dataProviderMapping.dataProvider is not a function" with this code – elsni Jan 16 '20 at 09:17
  • Make sure you specified all your resources ? – Gildas Garcia Jan 16 '20 at 13:10
  • yes, instead of dataProviderMapping.dataProvider(type, resource, params) I can return dataProviderMapping.dataProvider.getList(resource, params) which works in the grid view but not for the edit view. – elsni Jan 17 '20 at 14:41
  • 1
    I can return dataProviderMapping.dataProvider but not dataProviderMapping.dataProvider(type, resource, params), but it does not work anyway – elsni Jan 23 '20 at 12:36
  • Even when I return the correct dataProvider object I get an error: TypeError: dataProvider[type](...).then is not a function – elsni Jan 23 '20 at 13:06
  • 1
    SimpleRestProvider returns a DataProvider object, not a callback. So when you try to make the call passing the parameters it fails because it is not a function. I'm not sure if this example refers to older code? – wattry Feb 19 '20 at 20:59
  • Yes, this example was targeting react-admin v2 – Gildas Garcia Mar 04 '20 at 07:15
5

If you're wondering how to have multiple data sources as well as multiple dataProvider types i.e. rest, GraphQL e.t.c. The v3 docs state:

In react-admin v2, Data Providers used to be functions, not objects. React-admin v3 can detect a legacy Data Provider and wrap an object around it. So Data Providers developed for react-admin v2 still work with react-admin v3.

This means that the dataProvider property on the <Admin> component can take:

  • An object of type DataProvider
  • A function that provides (type, resource, params) returning a promise containing the data.

This means that if you provide your DataProvider is a function, you will have to create an adapter and call the functions on your provider manually. Here is an example.

./dataProviders.js

import { gqlDataProvider, restGQLDataProvider, restProvider } from './buildDataProviders';

// The first assumption is that all resources are unique, thus we can use an object or a map instead of an array. Key = resource, value = DataProvider.
const dataProviders = new Map([
  ['users', restGQLDataProvider],
  ['users-local', restProvider],
  ['Invoice', gqlDataProvider],
  ['Debtor',  gqlDataProvider],
  ['Status',  gqlDataProvider]
]);

export default async (type, resource, params) => {
  // Get the DataProvider from the map.
  const dataProvider = await dataProviders.get(resource);

  // The DataProvider object is wrapped in a function so we can pass in the parameters to get the result.
  if (dataProvider instanceof Function) {
    return dataProvider(type, resource, params);
  }
};

./buildDataProvider.js

import { gql } from 'apollo-boost';
import buildGraphQLProvider from 'ra-data-graphql-simple';
import { gqlClient, restClient } from './apolloClients';
import simpleRestProvider from 'ra-data-simple-rest';

const LOCAL_REST_ENDPOINT = 'http://localhost:3001/api';

export RestProvider = simpleRestProvider(LOCAL_REST_ENDPOINT);

export const gqlDataProvider = buildGraphQLProvider({
  client: gqlClient
});

// This is just an example of the getList provider, you'll have to write the rest.
export const restProvider = (type, resource, params) => {
  const providerMap = new Map([
    ['GET_LIST', async () => { 
      return await RestProvider.getList(resource, params);
    }]
   ])

export const restGQLDataProvider = (type, resource, params) => {
  const providerMap = new Map([
    ['GET_LIST', async () => {
      const query = gql`
        query ${resource} {
          ${resource} @rest(type: "${resource}", path: "/${resource}") {
              id
              name
              username
              email
              address
              phone
              website
              company
            }
          }
        `;
      
      const result = await restClient.query({ query });
          
      return { data: result.data[resource], total: result.data[resource].length };
    }]
  ])
}

./apolloClients.js

import { ApolloClient, ApolloLink, InMemoryCache, HttpLink } from 'apollo-boost';
import { RestLink } from 'apollo-link-rest';
import auth from '../../auth';

const GRAPHQL_ENDPOINT = 'http://localhost:4000';
const REST_ENDPOINT = 'http://jsonplaceholder.typicode.com';
const httpLink = new HttpLink({ uri: GRAPHQL_ENDPOINT });
const authLink = new ApolloLink((operation, forward) => {
  operation.setContext({
    headers: {
      'x-api-key': auth.aws_appsync_apiKey
    }
  });

  return forward(operation);
});

export const restClient = new ApolloClient({
  link: new RestLink({ uri: REST_ENDPOINT }),
  cache: new InMemoryCache()
});

export const gqlClient = new ApolloClient({
  link: authLink.concat(httpLink),
  cache: new InMemoryCache()
});
wattry
  • 936
  • 10
  • 22
1

I was faced with the same problem in the new version 3.1

I decided to make a universal dataProvider

dataProvider.js

import {fetchUtils} from 'ra-core';
import ApiEndpoints from '../config/ApiEndpoints';
import {
 restGetList,
 restGetOne,
 restGetMany,
 restGetManyReference,
 restUpdate,
 restUpdateMany,
 restCreate,
 restDelete,
 restDeleteMany,
} from './rest/index';
import {CONSTANTS} from '../constants';

const httpClientTui = (url, options = {}) => {
 if (!options.headers) {
  // eslint-disable-next-line no-param-reassign
  options.headers = new Headers({Accept: 'application/json'});
 }
 const token = localStorage.getItem(CONSTANTS.CMS_KEY.TOKEN);
 options.headers.set('Authorization', `Bearer ${token}`);
 return fetchUtils.fetchJson(url, options);
};

function selectApiEndpoint(resource = '') {
 const result = {};
 result.apiEndpoint = 'https://jsonplaceholder.typicode.com'; // default parameters
 result.apiType = 'rest';
 Object.keys(ApiEndpoints).forEach((key) => {
  if (ApiEndpoints[key].url.includes(resource)) {
   result.apiEndpoint = key.toString();
   result.apiType = ApiEndpoints[key].type.toString();
  }
 });
 return {
  ...result,
 };
}

export default (apiUrl, httpClient = httpClientTui) => ({
 getList: (resource, params) => {
  const {apiEndpoint, apiType} = selectApiEndpoint(resource);
  switch (apiType) {
   case 'rest':
    return restGetList(apiEndpoint, resource, params, httpClient);
   default:
    return restGetList(apiEndpoint, resource, params, httpClient);
  }
 },

 getOne: (resource, params) => {
  const {apiEndpoint, apiType} = selectApiEndpoint(resource);
  switch (apiType) {
   case 'rest':
    return restGetOne(apiEndpoint, resource, params, httpClient);
   default:
    return restGetOne(apiEndpoint, resource, params, httpClient);
  }
 },

 ...
});

ApiEndpoints.js

module.exports = {
 'https://jsonplaceholder.typicode.com': {
  type: 'rest',
  url: [
   'posts',
   'comments',
   'albums',
   'photos',
   'todos',
   'users',
  ],
 },
};

./rest/index.js

import {fetchUtils} from 'ra-core';
import {stringify} from 'query-string';

/**
 * From ra-data-json-server
 * https://github.com/marmelab/react-admin/blob/master/packages/ra-data-json-server/src/index.ts
 */

/**
 * Get list
 * @param apiEndpoint {string} - domain
 * @param resource {string} - URI request
 * @param params {object} - params request
 * @param httpClient {function}
 * @returns {{data: object, total: number}}
 */
export function restGetList(apiEndpoint, resource, params, httpClient) {
 const {page, perPage} = params.pagination;
 const {field, order} = params.sort;
 const query = {
  ...fetchUtils.flattenObject(params.filter),
  _sort: field,
  _order: order,
  _start: (page - 1) * perPage,
  _end: page * perPage,
 };
 const url = `${apiEndpoint}/${resource}?${stringify(query)}`;

 return httpClient(url).then(({headers, json}) => {
  if (!headers.has('x-total-count')) {
   throw new Error(
    'not found x-total-count',
   );
  }
  return {
   data: json,
   total: parseInt(
    headers.get('x-total-count').split('/').pop()
   ),
  };
 });
}

/**
 * Get one record
 * @param apiEndpoint {string} - domain
 * @param resource {string} - URI request
 * @param params {object} - params request
 * @param httpClient {function}
 * @returns {{data: object}}
 */
export function restGetOne(apiEndpoint, resource, params, httpClient) {
 return httpClient(`${apiEndpoint}/${resource}/${params.id}`).then(({json}) => ({
  data: json,
 }));
}

...