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 functio
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:
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()
});
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);
};
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,
}));
}
...