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

6

19

If I have multiple distinct REST API back-ends (separate apps doing separate things), and I want a single UI (react-admin-based) app that is capable of doing CRUD to entities managed by these various back-ends, I'm curious if it's possible to wire up react-admin to do that.

I'm imagining that instead of this (single/global dataProvider):

const App = () => (
    <Admin dataProvider={simpleRestProvider('http://path.to.foo.api')}>
        <Resource name="foos" list={FooList} />
    </Admin>
);

we could do something like this (resource-specific dataProviders):

    const App = () => (
        <Admin >
            <Resource name="foos" list={FooList} 
               dataProvider={simpleRestProvider('http://path.to.foo.api')} />
            <Resource name="bars" list={BarList} 
               dataProvider={simpleRestProvider('http://path.to.bar.api')} />
        </Admin>
    );

At any rate, if you have advice on how I can do REST to multiple back-ends in react-admin, I'd appreciate it very much.

Gilreath answered 6/6, 2018 at 16:6 Comment(0)
A
17

No, but you can have a super dataProvivder which would select the appropriate one depending on the resource. Something like:

const dataProviders = [
    { dataProvider: simpleRestProvider('http://path.to.foo.api'), resources: ['foos'] },
    { dataProvider: simpleRestProvider('http://path.to.bar.api'), resources: ['bars'] },
];

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

    return dataProviderMapping.dataProvider(type, resource, params);
}
Annorah answered 8/7, 2018 at 5:22 Comment(2)
I'm trying to use your code, can you check my answer? #52890409 thanks!Hildegard
This is great! But I think the found data provider would still be a pair of a provider and resources, you have to insert dataProvider. dataProvider(type, resource, params) in order to make it work.Smooth
R
9

Solution 2021 with React admin version 3.12.x above

I got the idea from @Gildas Garcia's answer. Thank @Gildas Garcia very much. But your code only work on React admin version 2, in version 3 and above, I need have some customs to make it work. Here is my solutions

// ... others import
import {
  fetchUtils,
  GET_LIST,
  GET_ONE,
  CREATE,
  UPDATE,
  UPDATE_MANY,
  DELETE,
  GET_MANY,
  GET_MANY_REFERENCE,
} from 'react-admin';

const dataProviders = [ 
  {
    dataProvider: simpleRestProvider('http://localhost:3000'),
    resources: ['users'],
  },
  {
    dataProvider: simpleRestProvider('http://localhost:3002'),
    resources: ['roles'],
  },
  {
    dataProvider: customJSONDataProvider('http://localhost:3003'),
    resources: ['permissions'],
  },
];

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

  const mappingType = {
    [GET_LIST]: 'getList',
    [GET_ONE]: 'getOne',
    [GET_MANY]: 'getMany',
    [GET_MANY_REFERENCE]: 'getManyReference',
    [CREATE]: 'create',
    [UPDATE]: 'update',
    [UPDATE_MANY]: 'updateMany',
    [DELETE]: 'delete',
  };

  return dataProviderMapping.dataProvider[mappingType[type]](resource, params);
};
Revolve answered 24/2, 2021 at 9:24 Comment(2)
best answer for the recent version, thanksBalzer
Works well, thank you.Ewing
G
5

React Admin 3 employs a new interface for dataProviders, and all answers seem to work only with React Admin 2.

Here's what I did to make things work on React Admin 3:

class CompositeDataProvider {
  constructor(dataProviders) {
    this.dataProviders = dataProviders;
  }

  _delegate(name, resource, params) {
    const { dataProvider } = this.dataProviders.find((dp) =>
      dp.resources.includes(resource)
    );

    return dataProvider[name](resource, params);
  }

  getList(resource, params) {
    return this._delegate("getList", resource, params);
  }
  getOne(resource, params) {
    return this._delegate("getOne", resource, params);
  }
  getMany(resource, params) {
    return this._delegate("getMany", resource, params);
  }
  getManyReference(resource, params) {
    return this._delegate("getManyReference", resource, params);
  }
  create(resource, params) {
    return this._delegate("create", resource, params);
  }
  update(resource, params) {
    return this._delegate("update", resource, params);
  }
  updateMany(resource, params) {
    return this._delegate("updateMany", resource, params);
  }
  delete(resource, params) {
    return this._delegate("delete", resource, params);
  }
  deleteMany(resource, params) {
    return this._delegate("deleteMany", resource, params);
  }
}

export default CompositeDataProvider;

You can then use it in this way:

const dataProvider = new compositeDataProvider([
  {
    dataProvider: jsonServerProvider("https://jsonplaceholder.typicode.com"),
    resources: ["posts", "users"],
  },
  {
    dataProvider: simpleRestProvider("http://path.to.bar.api"),
    resources: ["bars"],
  },
]);

It's still barebones but it gives you an idea you can improve on.

Garald answered 2/10, 2020 at 20:33 Comment(0)
I
4

In the README of recent ra release (3.4.1), there is a section (Extending a Data Provider (Example of File Upload)) that mentions: "... combine two Data Providers for two backends into a single object...". You can write a wrapper with branches to relay to different provider's implementation. An example to add your real data provider on top of their demo is:

// new file dataProvider/super.js
import simpleRestProvider from 'ra-data-simple-rest';
import delayedDataProvider from './rest'

const realDataProvider = simpleRestProvider('http://real_world_url/');

const myDataProvider = {
    ...delayedDataProvider,
    getList: (resource, params) => {
        console.log(resource)
        if (["customers", "reviews", "commands"].includes(resource)) {
            // resources for the demo
            return delayedDataProvider.getList(resource, params)
        } else {
            // your own resources
            return realDataProvider.getList(resource, params)
        }
    },
};

export default myDataProvider;

and replace the delayedDataProvider with the above myDataProvider

// file dataProvider/index.js
         case 'graphql':
             return import('./graphql').then(factory => factory.default());
         default:
-            return import('./rest').then(provider => provider.default);
+            return import('./super').then(provider => provider.default);

Disclaimer: I got the same error as previous posts "provider is not function..."

Intrench answered 19/4, 2020 at 15:31 Comment(0)
G
3

Since react-admin version 4, it is now possible to have multiple DataProviders out of the box:

https://marmelab.com/react-admin/DataProviders.html#combining-data-providers

Golgotha answered 12/5, 2022 at 18:36 Comment(0)
M
0

you can make custom of resource choose to which api you will use. one admin only have one dataProvider.

      <Admin
        dataProvider={superDataProvider}
     />

but you can do like this:

 import superDataProvider from './dataProviderFactory';

following is my code you can reference

import dataProviderRuby from './dataProvider'; //handle ruby dataProvider
import dataProviderJava from './dataProviderForJava';// handle java dataProvider
import { rubyClient, javaClient } from './apolloClient';//custom two diff client one will fetch ruby service ,other will java

const IsOmsResource = resource => {
  const omsReource = [
    'markets',
    'regions',
    'countries',
    'states',
    'items',
    'salesOrganizations',
  ];
  return omsReource.some(ele => ele === resource);
}; //those resource will fetch data from java service others will go to ruby

const rubyDataProvider = async (type, resource, params) => {
  const provider = await dataProviderRuby({
    client: rubyClient,
  });
  return provider(type, resource, params);
};

const javaDataProvider = async (type, resource, params) => {
  const provider = await dataProviderJava({
    client: javaClient,
  });
  return provider(type, resource, params);
};

const superDataProvider = (type, resource, params) => {
  if (IsOmsResource(resource)) {
    console.log('当前Java', resource);
    return javaDataProvider(type, resource, params);
  }

  console.log('当前ruby', resource);
  return rubyDataProvider(type, resource, params);
};

export default superDataProvider;

following is the './apolloClient'

import ApolloClient from 'apollo-client';
import { createHttpLink } from 'apollo-link-http';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { setContext } from 'apollo-link-context';

const httpLinkRuby = createHttpLink({
  uri: '/graphql',
});
const httpLinkJava = createHttpLink({
  uri: '/oms-graphql',
});
const authLink = setContext((_, { headers }) => {
  const token = localStorage.getItem('token');
  return {
    headers: {
      ...headers,
      authorization: token ? `Bearer ${token}` : '',
    },
  };
});
export const rubyClient = new ApolloClient({
  link: httpLinkRuby,
  cache: new InMemoryCache(),
});
export const javaClient = new ApolloClient({
  link: authLink.concat(httpLinkJava),
  cache: new InMemoryCache(),
});
Mazarin answered 12/9, 2019 at 4:7 Comment(1)
i get error "provider is not function" at return provider(type, resource, params);Coinage

© 2022 - 2024 — McMap. All rights reserved.