Closed tmeasday closed 7 years ago
I know I used the word "context" when we talked about this before, but perhaps we can just call it a "component"? Or is that confusing? Just worried about using a relatively generic word like "context" which could mean almost anything.
Some specific questions:
context.query
return? Is it like watchQuery
and returns an observable?context
and ownProps
re-run whenever either of those change? Could that be too costly?I like this idea because it seems to match up with the way Meteor subscriptions work, which people really like as far as I know.
You know what's kinda funny - this is somewhat similar to: https://github.com/stubailo/meteor-reactive-method which is also a package which fakes a declarative API over an imperative action.
Oh, one final question - if the goal here is to unify React/Angular integrations, what does the "Core API" look like? Is it:
const context = client.context();
context.watch(() => {
const result = context.query(...);
displayData(result); // whatever custom function you have
});
Almost seems like the context should return an observable here:
observable = context.observe(() => {
const result = context.query(...);
return {
key: result,
}
});
Then teardown
can just be unsubscribe
.
I agree context is not a good name, I just didn't want to debate that yet. Maybe component query?
Your API could work and is probably minimally weird.
Why diverge from syntax that people are already familiar with via @connect? Both ngRedux and react-redux utilize the connect function to provide a layer of indirection so that your code is less bound to a store instance.
In regards to
If the focus is to unify view layer integration how does this look in Angular2? Interesting to first change React Apollo without considerations to other view layers concurrently?
If there are already best practices being built in each respective community, why diverge from that for the sake of creating a new standard ? Shouldn't the goal be to embrace each community, have contributors that are thinking about these problems with the framework in mind, and executing changes when they occur? Or is there not enough volume of those people?
I guess it really comes down to - do we want to maintain completely separate docs for Angular and React, and make constant changes to those APIs for every new feature in core?
It could be fine, we've been doing that with react-apollo for a while.
If the focus is to unify view layer integration how does this look in Angular2?
One of the goals of this post is to get feedback from Angular devs - if we don't write up a design we can't get the feedback!
But I see what you're saying - if we want to feel like a really natural thing for different view layers, those are going to have different conventions in their community. It probably isn't worth giving up that natural feel to unify the API if that makes it worse for both communities.
BTW - the API we are proposing here is actually more similar to Redux connect, since it basically comes down to using selectors that re-run on store change. The previous API (mapQueriesToProps) was decidedly not like connect
, since it didn't let you write your own selectors and transformations. But I think it needs some massaging to get there.
@abhiaiyer91
Both ngRedux and react-redux utilize the connect function to provide a layer of indirection so that your code is less bound to a store instance.
I'm interested as to why you mention this--a big part of my thinking behind the "proposed" part of this design is exactly this philosophy. For instance, my major gripe with the current 0.4.0 react-apollo
API is that the default props for the wrapped component looks like:
X.props = {
someQueryName: {
loading: true,
field1: data,
field2: data,
refetch: function() ..
}
}
Which means that X
at some level needs to understand that its input (field1
in this case) comes from a query called someQueryName
and there are all these extra props (like refetch
) in there that are likely not relevant to the presentational component.
(It should be noted that @jbaxleyiii added a mapResultsToProps
to solve this exact problem, and it's great, but as mentioned above, it'd be nicer not to have multiple callbacks).
I agree with 2.
Which 2 was that? ;)
Interesting to first change React Apollo without considerations to other view layers concurrently?
Actually I did look at the angular integration and it looks conceptually very similar to the React one right now. I'm just not confident enough about Angular terminology to write it up my proposed design in Angular-language yet. I have asked @Urigo to take a close look at this issue though.
If there are already best practices being built in each respective community, why diverge from that for the sake of creating a new standard?
I definitely don't want to do this!
I'm not completely certain what the current best practice in the React community is for data sources with the following properties:
refetch
, etc), and the user needs to select what they need.I'm modelling the above very much on the spirit of redux (which is the direction @jbaxleyiii is going in too, I think). But maybe there's a data access library we should be looking at?
Thanks for the clarification @tmeasday
I'm interested as to why you mention this--a big part of my thinking behind the "proposed" part of this design is exactly this philosophy.
Ah because their interaction with connect functions are just different syntaxes. Im just on the side of letting the Angular nerds and React nerds have the same exact api they are already using but enhanced for our purposes.
Which 2 was that? ;)
This number 2 mate!
Client integrations must be continually updated to provide the same shape of API as Apollo Client develops.
Ah I get it now @tmeasday
It just seems this
@graphql((context, ownProps)
Should read
@graphql(mapStateToProps, mapDispatchToProps, mapQueriesToProps, mapMutationsToProps)
and both mapStateToProps
and mapResultsToProps
take ownProps as their second argument.
Maybe i was lost in translation a bit!
The great thing about Redux is state is whatever you want it to be. As long as youre returning a new state. So this here something we can spin, as at Workpop we've done some nifty things storing a function in the store. Worked well for passing around function callbacks to different components.
So i'm all about creating a standard for this GraphQL Redux interaction.
Just to be clear, we aren't proposing using this syntax to interact with the redux store directly. If you want to do that, it seems best to just use another container:
@connect(
(state, ownProps) => {
return { id: state.feed.id },
},
(dispatch, ownProps) => {
return { handleError(error) { return dispatch(handleError(error)) },
});
// here ownProps will come down from the connect container, i.e. include `id` and `handleError`
@graphql((context, ownProps) => {
// as above
});
class Feed ...
Interesting. I'm actually down for this proposal, but I want to poke more.
Why separate state connectors here between UI State and GraphQL State. The beauty of Redux is the ability to colocate this data. Now, it took me a while to get used to, and MAYBE this separation will help people grok it better?
Whats your thinking there?
I'd actually be interested in an API like
@connect((ownProps, context, state, dispatch) => ...);
But the argument against it is that it's probably more confusing for people who are following along with our examples to see Redux done in an a different way to what they are used to. Perhaps this API can be exposed for "advanced" users if we like it.
(PS I'm not sure why Redux separates mapStateToProps
and mapDispatchToProps
, it seems unnecessarily confusing and also slightly restrictive).
But the argument against it is that it's probably more confusing for people who are following along with our examples to see Redux done in an a different way to what they are used to.
Yeah this is the thing I'm most worried about. Ideally a uniform experience for Redux users and maybe some power user apis.
Where I'm coming from is, I'd like people to have a jumpstart with easy wins in Apollo Client. Then when they become rockstars, start diving into deeper APIs.
Is that an okay thought?
Seems like a good thought on the surface, but I'm not sure which API it pushes us toward?
Definitely. In any case, I've exhausted my poking of the design! Let's do it!
Yeah I was thinking this might actually be easier for Redux devs because they can use the connect
they are used to already.
It would be interesting to get the Angular take on this.
I've thought about this for a couple days. All in all I'm not a fan of moving state management into the core client.
It's confusing for people to look at the Apollo Client API docs for details of the options etc, and not be looking at the API that they actually call.
I agree that this can be confusing. However, looking at prior art like redux, when libraries choose to be view layer independent, this happens and isn't a bad thing. It helps with triage of issues because the API is a giveaway of where to start looking.
Client integrations must be continually updated to provide the same shape of API as Apollo Client develops.
This was true in the first version of react-apollo, but in the current and the @next
version, we pass through all additions to the client API. Also, as long as they are separate projects, there will always be a potential change that means the client can / will be ahead of the view layer.
There's a significant amount of shared logic (for instance, around not re-querying when query arguments don't change) that could be shared between client implementations.
I think an easy (and beneficial) way to solve that is to expose any critical logic as part of the exported methods in the client (not top level)
if the goal here is to unify React/Angular integrations
I don't really think this is a good idea. View layers (and communities) have different conventions and expectations for API design. For instance, we stick the client on the context of react, but in angular, other view layers you would just import the created client for usage. To unify this, we would need to recommend people import the created client for the react integration otherwise we would still have different API's between layers.
So I would say if the goal is to unify view layers, then the view integrations should be part of the client core just like it is with the apollo-server. Then create a universal api and import { reactApollo } from "apollo-client"
I'm not a fan of this personally but ¯\_(ツ)_/¯
.
I think everything you're saying makes sense to me. Let's let this cook for a bit more, but my current inclination is that we should just figure out a way to improve our documentation so that people can easily figure out the right syntax for their platform.
So I agree (especially after just releasing react-apollo@0.4
) that focusing on changing APIs will bring little benefit at this point (and could be a step backwards besides).
I wonder what the appetite is for moving some of the common logic into Apollo Client would be (so perhaps still having a Context object but perhaps not presenting it to the user?)
I think a lot of the code around checking if arguments have changed and keeping track of old results could live there if we think it's something that would generalise across view layers (my intuition is yes).
What do you think @jbaxleyiii? If you think it's a good idea I can make the changes to AC and then send some PRs.
@tmeasday I would love that!
I like the idea. Decorators are just functions so angular2-apollo
could use the @graphql
inside the @Apollo
decorator. The whole logic, which does pretty much the same thing as the react-apollo
would be moved to @graphql()
.
Life cycle hooks would be in charge of controlling the context
.
:clap: :+1:
See #554 for first pass design.
@tmeasday do you think this can be closed now? Or is there more work to do?
I think so! Will be getting the PR for RA merged soon. Angular folks might want to take a look at the diff: https://github.com/apollostack/react-apollo/pull/211/files
Anything else we want to do can be tracked in separate issues moving forward.
This is a proposal to centralize the "state" management part of Apollo Client integrations inside the JavaScript client.
State of the art
I'll speak chiefly about the
react-apollo
integration, although it appears the same rationale would apply to the Angular integration also.The current API for
react-apollo
adds a layer between the user andapollo-client
, in both directions:react-apollo
APIreact-apollo
API.Rationale for change
It seems like there are three main reasons why doing it this way isn't ideal:
Basic Design
The basic commonality of any view layer is the concept of a visual component that has changing arguments and state, but is not rendered from scratch each time, but instead tries to reflect the new desired state with minimal work.
So new concept proposed in Apollo Client is the "context". The idea is that each data-aware component in the view layer would have a corresponding Apollo Client context. The context would offer a
context.query()
API with the following behaviour:context.query()
would always hit the server, even if the results are known.context.query()
would have no effect if the arguments or variables have not changed (This avoids the integration needing to understand the arguments toquery
and diff-ing them)context.teardown()
method which would clean up all resources associated with the context (so the integration does not have to understand the resources associated with a component).What does a specific integration actually need to do?
teardown
at the appropriate point.Tom's Proposed
react-apollo
APIAgain, I think that a lot of this would apply to the Angular integration too.
Suppose that
context.query()
returned the results as we currently understand them (rather than a promise or an observable), and there's (something like) an additionalcontext.on('change')
API that the integration can monitor for point 4 above. This is a tracker-ish API I guess.Then the API for a container could be something like
Apart from the rationale above, the advantages I see in this API over the current 0.4.0 API are:
context.query()
yourself as props, which means your@graphql
function can be super simple.mutate
into an event handler callback at any point you like in any form you like.