Open cellis opened 7 years ago
Before we coming to mutations, we should complete preparation of our relayStore
:
relayStore.mutate()
method chainable via Promise. This will allows to us using async/await for complex data flow with mutations.relayStore.fetch()
method for fetching data from the server without hitting relay-router and component fragments. We will make code refactoring and start using relayStore
on full power.Right now I implement this and provide additional instructions.
So let practice with relayStore.fetch
method. Mutate
method has analogous symantics with additional arguments (we will consider it tomorrow, just a little patience).
So lets refactorapp/components/categories/ToggleCategory.js
component which use manual data fetching.
// native Relay API
toggle() {
this.setState({
isOpen: !this.state.isOpen,
});
if (!this.state.data) {
const query = Relay.createQuery(
Relay.QL`query {
viewer {
category(filter: $filter) {
${Category.getFragment('category')}
}
}
}`,
{ filter: { categoryID: this.props.id } }
);
relayStore.primeCache({ query }, readyState => {
if (readyState.done) {
const data = relayStore.readQuery(query)[0];
this.setState({ data: data.category });
}
});
}
}
As you can see it is difficult for reading (Relay.createQuery
, primeCache
, readyState
, relayStore.readQuery
). Agrrrr.
So let rewrite it with our new relayStore.fetch
method
// our wrapper on relayStore, with simplified API
toggle() {
this.setState({ isOpen: !this.state.isOpen });
if (!this.state.data) {
relayStore
.fetch({
query: Relay.QL`query {
viewer {
category(filter: $filter) {
${Category.getFragment('category')}
}
}
}`,
variables: {
filter: { categoryID: this.props.id },
},
})
.then((res) => {
// NB: Relay does not return root field, in this case `viewer`
// so in responce we get object with fields from `viewer` (may be changed in Relay2)
this.setState({ data: res.category });
});
}
}
This is much much better.
@cellis please refactor all other Toggle*
components (pull fresh version and create new branch).
When it will be completed let move to the server side. Clone https://github.com/nodkz/graphql-compose-examples and try to add mutations createOneProduct
and removeProduct
to the schema.
@cellis also I want you to pay attention to fragment inclusion Category.getFragment('category')
to the query. This is a replacement for fatQuery
in mutations. All data required explicitly (no more complex operation on mobile devices for determining additional fields for query). So when you provide fragment in such way, data automatically updates in the Relay Store, and store touch all components that subscribed on changed data for rerendering.
And keep in mind that mutations are the regular queries. The difference only in naming (pointing that this request will change data on server) and in serial execution if several mutations in the request (several queries executes in parallel). Say it again, cause it is important, mutations in GraphQL almost similar to the queries. When we complete with queries, it will be very easy to understand and construct complex mutations with deep data fetching in the payload.
@nodkz thanks for this. Will work on this refactor tomorrow after work.
@nodkz I'm looking into refactoring the Toggletoggle()
method at all? I will refactor anyways, but it seems like the act of toggling should instead be showing()
(perhaps by setState({ toggled: true })
) yet another sub container in relay with it's own route/"root" query. Anyways I will make it work like this, just wondering what the reasoning was. I think it could save time, it's just not documented on the relay website.
Called toggle
cause on pressing a button it should show/hide sub-component and change button text.
toggle() {
this.setState({ isOpen: !this.state.isOpen });
// ...
}
render() {
const { isOpen } = this.state;
return (
// ....
<Button onClick={this.toggle}>
{isOpen ? 'close' : 'open'}
</Button>
);
}
We may move out fetching data to another method for clearence:
toggle() {
const isOpen = !this.state.isOpen;
this.setState({ isOpen });
if (isOpen) this.fetch();
}
fetch() {
relayStore
.fetch({
query: Relay.QL`query {
viewer {
category(filter: $filter) {
${Category.getFragment('category')}
}
}
}`,
variables: {
filter: { categoryID: this.props.id },
},
})
.then((res) => {
this.setState({ data: res.category });
});
}
Also this code makes one fix, it always will refresh component state from RelayStore on opening. Right now it fetches data only once, and when we introduce mutations and run them we will see stale data from component store, not from relayStore.
BTW relayStore.fetch()
method should make http-request only first time for populating its cache. After that (when we hide data and show it again) Relay will run a new query but fulfill it from its cache without network request.
FYI you can use relayStore.fetch({ ..., force: true }) for force retrieving data from a server without clearing the cache.
Anyway, current toggle implementation it is some kind of hack. Cause we keep data for child in the component state. Maybe in future when will be introduced react-router@v4 and this hack will be somehow removed. Cause RRv4 allows deep nested routes.
But for now, for demo purposes of component approach power is the good solution.
@nodkz RRv4 doesn't work with isomorphic rendering ( https://github.com/relay-tools/react-router-relay/issues/193 ). So maybe we have to keep doing it this way :/
I do not use react-router-relay
. With its queries aggregation, it brings too many restrictions for my app. Most important:
filter
in parent component and filter
in child component)It was discussed here https://github.com/relay-tools/react-router-relay/issues/213 But with my solution, you may get a waterfall of queries. But for my app it is not a problem.
Ok, refactor if Toggle* complete. Will move to other repo later tonight to createProduct mutation.
Some tricks with auth and context discussed here https://github.com/nodkz/graphql-compose-examples/pull/2
@cellis if you want to use react-router-relay
you can use my isomorphic router middleware for koa@2
https://github.com/stoffern/koa-universal-relay-router
@stoffern I have been using it in my personal project. But right now react-router-relay is hardcoded to relay 0.9.3
which means it doesn't support the new Relay GraphQLMutation stuff.
I've already talked with @nodkz about this. Using this as a way to keep track. Essentially I want to know how to make a mutation that updates a hasMany, hasOne relationship.