-
Notifications
You must be signed in to change notification settings - Fork 786
Proposal: API changes to support multiple clients #464
Comments
I think this would be great! |
My mistake, this API is not possible (maybe there is a complicated solution), because I will update my proposal to something that should work easier. |
I just pushed a work-in-progress commit, in case someone wants to see working code: 1st8@63c42db#diff-ff711df5e0aa7417faaf90767b79ac8bR35 Looking forward to get feedback. Cheers! |
Instead of implementing a <ApolloProvider client={client} as="extraClient">
{...}
</ApolloProvider> I am curious, why would you want this feature? Also, why would this be better than passing a client instance directly to the export default graphql(MyQuery, {
client: ...,
})(MyComponent); …or if a subtree needed a different client rendering an I would prefer just passing in clients directly for an initial implementation. |
This was exactly my first idea, which is afaik not possible, because the client would have to be passed down under a unique key in the context depending on the What do you think of the following compromise that is somewhere between my HoC approach and your suggestion? const ExtraProvider = createApolloProvider({ for: 'extraClient' })
// We could create the default ApolloProvider with createApolloProvider({ for: 'default' }) too
ReactDOM.render(
<ApolloProvider client={client}>
<ExtraProvider client={extraClient}>
{...}
</ExtraProvider>
</ApolloProvider>,
rootEl
)
In my use-case I need to provide multiple clients to a subtree of the application [1] and additionally the endpoint url depends on state from previous parts of the application [2].
[1] => I need multiple clients present in the same subtree of my application, so
[2] => I can't create the clients ahead of time to pass them to Imagine a screen where the user provides a hostname and based on that a session with multiple clients on different endpoints is started. The user can also go back and change the host. Hope that answers your questions and makes my requirements understandable 😄 |
It’s possible. Just provide a map in context. We don’t need to put each client in a separate context key. So a context that looked like this: const context = {
apolloClients: {
default: ...,
foo: ...,
bar: ...,
},
}; The PropTypes.objectOf(PropTypes.instanceOf(ApolloClient))
I still think there is a much smaller feature we could create which allows you to build the complex system you need on top of it rather than building the complexity of this specific use case into What about allowing a user to pass a This would then allow you to implement a |
... and I could add more clients further down the component tree by mutating the map in the context? Is that a good idea or even legal?
Give me some time to think about this. Right now it seems to me, that this would limit the number of usable clients for a given component to one. EDIT: Well I could still implement the Other than that, do you have any thoughts on my last code example? |
You would just clone the context and then you wouldn’t be mutating it. const nextClientMap = { ...lastClientMap };
Wouldn’t your approach have this limit too? Given that
Is it the one you linked above? I’m not in love with the design as I’ve mentioned before, and it may also break |
No, I meant the one from #464 (comment) But nevermind!
Shit, you're right! I thought reassignment was also forbidden, but you can of course just shadow the previous context. So just to be safe, I will repeat your suggestion, which was also my initial idea. // provide clients, which share the same store, but with different reducer keys
ReactDOM.render(
<ApolloProvider client={client} store={store}>
<ApolloProvider client={extraClient} as="extraClient" store={store}>
{...}
</ApolloProvider>
</ApolloProvider>,
rootEl
)
// use selected clients
export default compose(
graphql(query),
graphql(specialQuery, { use: 'extraClient' }),
)(Component); I still see one smaller problem with this approach, which can be resolved later if necessary: |
Yeah, I’m concerned with the Redux collision as well. I would much prefer the solution I mentioned above where we allow you to pass in |
That makes sense. I will try to implement the basics later this week. |
I am also interested in this feature. Roughly, here is how I got it to work. I created my own provider, which just modifies the context (replace the client variable with a new client). That way all children of the tree will be scoped to a different GraphQL endpoint. import { PropTypes, PureComponent } from 'react';
import ApolloClient, { createNetworkInterface } from 'apollo-client';
const clients = {};
export default class CustomProvider extends PureComponent {
static propTypes = {
children: PropTypes.node,
uri: PropTypes.string.isRequired
};
static childContextTypes = {
client: PropTypes.object
};
getChildContext = () => {
const { uri } = this.props;
let client = clients[uri];
if (!client) {
client = new ApolloClient({
networkInterface: createNetworkInterface({
uri,
opts: {
credentials: 'include'
}
})
});
clients[uri] = client;
}
return {
...this.context,
client
};
}
render = () => this.props.children;
} Then, I just wrap components in this Provider which need a different client context:
It definitely does some weird stuff with redux though. I think it it creates a Store each time a client is new'd up, although I haven't look too closely at the react-apollo internals to understand what's really going on. Also, not to derail the thread, but this approach works with But in the latest version Official support (or at least documentation) for multiple clients would be great. I know it's a non-standard approach, and most people don't need this, but it fits my requirements really well, and makes the code quite elegant. |
@stevewillard it may be good to open a new issue for the error you mention with #462 This is the kind of DIY approach I’d like to enable, however, with our implementation 👍 |
I'd like to open an issue, but I wanted to try to isolate the problem and create a small repo for it first. it's kind of hard to explain without seeing a full example. |
@1st8 @stevewillard I'm interested in the use-case that you guys have that requires multiple clients. I think there may be a better solution that we could implement in the future, like a special network interface / executor function. |
A custom network interface was also something that crossed my mind, but I discarded it in order to avoid cache/store conflicts, when running two identical queries against multiple endpoints. |
While I was looking into the props based approach I just found a commit removing exactly that: 626dbb7 On the other hand: Would the updated proposal from my initial post stand a chance of being merged if it was submitted as PR? |
@1st8 we removed it to prevent accidental collisions from props that were named client (i.e. client="name of client" in a CRM application). I think this could be best implement initially via @calebmer suggestion:
@helfer this is a use case we are thinking through at NewSpring as we migrate our GraphQL endpoint from our js implementation to a dotnet one that is built into our CRM. We would like an incremental roll over to the new endpoint instead of a hard switch.
fwiw, this is my preferred implementation design
SSR should just work thanks to @tmeasday's refactor to prevent context collisions, but we need to verify tests passing of course @1st8 do you feel like you could implement this? Or do you want me to? |
Let me have a go at implementing it, seems we have everything together. 👍 Just to be clear: |
@1st8 I think sounds great! |
@helfer My company, https://curio.org/ provides a way for people to upload, share and visualize datasets. It's the beginnings of a Github for data concept. We have a general GraphQL API (https://api.curio.org/graphql), but we also dynamically create GraphQL endpoints for each dataset (Curio) that you create. The schema is based off of what fields exist in the data and of what type. When you navigate to a Curio page, I dynamically change the context (like in the example above), and issue a query:
Which is basically our introspection query to get all the fields and the types for the current Curio. When you switch to a different Curio page, the context changes, and the same query gets re-issued. I think the following would work for me, but it's a bit clunky:
I guess I could make the 'use' keyword based off the URL params? |
@stevewillard this is why I prefer a props based solution, or a solution that extends graphql(query, {
options: props => ({ client: ... }),
})(MyComponent) |
I’m still opposed to the |
@stevewillard Oh, that's an interesting use-case. I think the disadvantage of using multiple clients that have different schemas is that it would become a lot more difficult to statically analyze queries. But since the endpoints are dynamically generated, I assume that the same would be the case for your queries, so it shouldn't be an issue. Is there a reason it has to be multiple clients, or could it also be achieved with a smart network interface that knows which endpoint a query needs to be sent to? The answer to this question should come down to whether or not there could be naming collisions between the types in the two (or more schemas), and whether you can generate ids that are unique across schemas or not. |
Nope, there's no reason that I need to use multiple clients. I'd be happy (probably happier) with a custom network interface. I originally thought about creating a network interface, but I wasn't sure how to structure it. ids across schema will be unique, but the general structure (root queries, most fields) will be the same. Also, many types don't have ids at the moment :( As an example, for a dataset that a bunch of rows (items) about people, you might issue something like this:
But then the same-ish query could be executed for a different dataset about Hearthstone cards:
(The takeaway here is that the _source type, and it's fields are dependent on which dataset your'e viewing) It probably doesn't help that we don't follow a Relay-like convention for entities and connections, and we don't have ids for everything. I'm happy to restructure the API to be more accommodating, but the general feeling I have is that a custom network interface over multiple clients is preferable. |
@stevewillard Could you have a custom network interface that executes against a GraphQL schema that nests different schemas at different levels? For example: {
curio { item(limit: 3) { ... } }
hearthstone { item(limit: 3) { ... } }
} Something like this could easily be done using a GraphQL.js schema that lives in the client and not the server. Some pseudo-code: import { GraphQLObjectType, buildClientSchema, print } from 'graphql';
const QueryType = new GraphQLObjectType({
name: 'Query',
fields: {
curio: {
type: buildClientSchema(curioIntrospectionJson),
resolve: (source, args, context, { fieldNodes }) => {
// Print a GraphQL query using just the sub-selection set for this field.
// We will execute this query against the actual GraphQL schema it resolves to.
const query = print({
type: 'Document',
definitions: [{
type: 'OperationDefinition',
operation: 'query',
selectionSet: {
type: 'SelectionSet',
selections: fieldNodes.map(({ selectionSet }) => ({ type: 'InlineFragment', selectionSet })),
},
}],
});
return executeCurioGraphQLQuery(query);
},
},
// Same for Hearthstone and other data sets...
},
}); Then a network interface would look something like the network interface from I’d really love to see a dynamic architecture like this in production as it demonstrates the flexibility of GraphQL and the tooling that has been created around it. |
Thats a really interesting approach, never would have thought of building a schema like this in the client. I will play around with it and maybe it solves this whole multiple endpoints topic elegantly. |
Yeah - I think that might work. I'll try it out later this week. Thanks :) |
I never had a chance to implement exactly what what @calebmer was proposing, but I was able to successfully use the 1.x version of Apollo using a combination of changing the client context (as I mentioned earlier in this thread) for endpoint-specific Component trees, and also aliasing queries that are identical, but hit different endpoints. I'm pretty happy with our current set up now. Thanks for making Apollo flexible enough to support this non-standard use case! I hope that if you guys decide to make schema introspection part of this client, that it'll be optional or flexible to support multiple clients / dynamic schemas. |
👍 |
While we wait for proper multi client support, there's another hack you can use if you simply need use a different client in a child component:
Any graphql queries in |
This issue has been automatically marked as stale becuase it has not had recent activity. It will be closed if not further activity occurs. Thank you for your contributions to React Apollo! |
Bump. I still think this is highly needed. |
Looks like there was some progress just now! #729 |
Yeah i implemented the proposed change. Let me know if you need help on how to use it in your app. I prepared some lib to use react-apollo with multiple apollo-clients. I plan to open source it as thin layer on top off react-apollo. |
@flexzuu Fantastic - thanks! I'll wait for the updated documentation from you, as it's a little unclear from the PR exactly how it works. The library you mention would off course be even better, so please do share :) |
Released in 1.4.3! docs to come! |
Are there docs on this yet? Looking but haven't found anything yet. |
@jbaxleyiii Are there docs on this? I could only find mention of it in the Apollo Angular docs https://www.apollographql.com/docs/angular/features/multiple-clients.html. |
@vjpr here are the relevant types: Lines 18 to 37 in 55d06d9
My understanding is that you can now pass graphql(query, {
options: {
client: myClient
}
}) Would you mind sending a PR to the docs? Probably here: https://www.apollographql.com/docs/react/basics/setup.html#graphql-config-options |
For anyone who is still searching for how to use multiple clients, I did a repo for you. |
@github0013 's solution works well! |
Are there docs to this yet? Can't find client option anywhere |
ok, looks like you should be able to parse the client prop to query
|
Thanks, I figured it out and it's as you say, in my case I was importing a gql template query (apologies if my dev lingo is awful) and a config along with the query then exporting my component with the graphql function a bit like this, works quite well:
If I didn't define a client it would default to the one I set up in the provider. |
I'm finding that this works for queries, but mutate calls are referencing the "default" endpoint rather than the one provided by under client.options. Is anybody else seeing the same?
Query pulls my data just fine, but on submission i'm finding that the mutation references the default client's endpoint. line 117 of Mutation.tsx has Would adding "props.client" be the way to go or am I just adding the client wrong? |
Hi I'm having the same problem as @jrounsav, query works well but mutations don't. Did you were able to find the solution? Thanks |
@victorgb6 I don't suppose you're using AppSync as well? |
I have been working for this workaround with no problem:
and then back in my code I use I come from Angular world and it was pretty easy to set up several clients but i cannot see that in React. Let me know if this works for you. |
Handling Multiple Graphql Clients in React. Below is my approach.
GraphqlApolloClients.js file const defaultHttpLink = new HttpLink({ uri: '/graphql' }); App.js SampleComponent.jsx |
Hey,
I would like to add support for multiple clients, similar to http://dev.apollodata.com/angular2/multiple-clients.html
Therefore I am proposing the following API changes and would like to have feedback before preparing a PR for this.
ApolloProvider
graphql
New config key
use
to select the client to be used for the given statement.Cheers!
The text was updated successfully, but these errors were encountered: