apollo-tote
apollo-tote copied to clipboard
Discussion of this API
Inspired by your work here, I've created two render prop components. One does queries and the other mutations. The API has turned out really nice, and I'd like to talk about it if you're interested.
Jay
Hey, sorry I don't get notifications for some reason 😢
would love to hear more!
No worries! I'm on the small screen, so I'll keep it brief for now.
The trend seems to be away from dynamic graphql and towards static, and I wanted the components to be aligned with that. The top-level API consists of two component factories: withQuery
and withMutation
. They are given the AST and return render prop components. Basic usage:
const findUserById = gql`
query findUserById($userId: ID) {...}
`
const FindUserById = withQuery(findUserById)
const MyComponent = ({userId}) => (
<FindUserById userId={userId}>
{({user}) =>
<h1>{user.name}</h1>
}
</FindUserById>
)
Mutations work similarly, except the component just provides a mutate
function, similar to the way Apollo works. The main difference is that you can pass props into the mutate component, and pass an object into the mutate
function, and all of those values will become variables for the mutation:
const updateUser = gql`...`
const UpdateUser = withMutation(updateUser)
const MyComponent = ({userId}) => (
<UpdateUser userId={userId}>
{mutate =>
<Button onClick={() => mutate({name: 'Foo'}).then(...)}></Button>
}
</UpdateUser>
)
@jcheroske I am probably missing a reason why to have those factory functions, what is the benefit instead of passing query as a prop?
I guess there are several benefits. For starters, the Apollo and Facebook teams are encouraging us to use static graphql. People much smarter than I am are saying that's the way to do it, and I'm inclined to follow along. Additionally, the Apollo HOC works this way, and since my components are just thin wrappers around the HOC, it makes sense to follow a similar pattern. I also read about some compile time optimizations that Facebook uses, and those only seem to work with static graphql. Finally, I can't see a compelling reason to make the AST a runtime prop. It's trivial to create insert and update components, for example, and choose between them if you need to select different operations at runtime.
I'll post the code for my components, as well as some examples to illustrate some of the patterns I have found, none of which require a runtime AST prop.
@jcheroske I would totally be open for a PR to make this happen :)
I'm on the touchscreen again, so I can't post the code right now, but, since you're here, I wanted to tease something. One of the headaches with Apollo is refetching queries after a mutation. You end up with this ugly code in your view layer where the component doing the mutation has to know about the queries that are affected by that mutation and refetch them. What I did was built a trivial dependency graph API that allows you to, when creating a query or mutation, to specify the tables and operations it affects, or is affected by. So, a mutation that adds a user would declare that it affects user inserts. A query that lists users would declare that it depends on user inserts and user deletes. Then, a component can just use the mutation component and refetch queries will be automatically called. It moves ugly refetch queries code out of the view layer and into a reusable database component layer.
What I have found, thinking more broadly, is that being able to componentize queries and mutations enables the creation of a full featured database access layer with minimal effort. Repetitive code, like looking up the currently logged in user and passing it as a query parameter or mutation variable, can be moved into the database components themselves and hidden from clients of the database component. This greatly simplifies the view code.
😍 would LOVE to see what you've got going!!! That is super exciting. I've personally felt that in the past but between features vs. optimization in an early stage scenario, I have to go with the former.
Let me know how I can help make that happen!
@jcheroske
and since my components are just thin wrappers around the HOC, it makes sense to follow a similar pattern.
Yea, I was thinking about wrapping the HOC too, but it feels kinda dirty. Lately, I am trying to get rid of as many HOC as possible simply because it's a nightmare to navigate through that in React Devtools :)
Considering that Tote would be straightforward implementation without wrapping HOC, I think that passing query directly as a prop works just fine. The query is already AST converted by gql
helper, so there is no real optimization coming from having it separately. Or perhaps I am missing out something obvious here...
One of the headaches with Apollo is refetching queries after a mutation. You end up with this ugly code in your view layer where the component doing the mutation has to know about the queries that are affected by that mutation and refetch them.
Yea I remember this headache, but I think it can be approached more reactively instead of some crude dependency tree. I've even gone that far to keep mutation call outside of the component and it's sitting in mobx-state-tree model instead. But it can be in a component too for sure.
import { types } from 'mobx-state-tree'
const UserStore = types
.model('User', {
branchId: types.maybe(types.string),
})
.actions(self => ({
selectPickupBranch: flow(function* selectPickupBranch(branchId) {
const { data } = yield self.apolloClient.mutate(...)
self.branchId = data.setBranchForOrder.id
})
})
Then every query that should be refetched based on changed branchId
is simply observing it with MobX and it works like a charm. Nice separation of concern without much of extra hassle. With Tote this would look even better.
const ShopTitle = () => (
<Observe
render={store => (
<ApolloTote
query={ShopTitleQuery}
variables={{ branchId: store.user.branchId }}
render={data => <h4>{getShopTitle(data)}</h4>}
/>
)}
/>
)
I am well aware this is one of the use cases and it needs more thinking and experimenting, but I think it's an interesting way to go.
I want to share the approach I took regarding render props and graphql. I am not using apollo-tote
simply because it's missing many things although it was a great deal of inspiration I gained from here. I basically have a dynamic wrapper around graphql
HoC. It works pretty well, especially with TypeScript.
https://gist.github.com/FredyC/655d562e7fd72fd5fa791042fda008c7#gistcomment-2366876