React Apollo vs Redux + REST: Implementing the same feature twice
Why we had to do the same feature twice
Because of the way things have evolved in Wix, we have separate developers working on web and mobile. In mobile we are using React Native, so theoretically the same person could work on both platforms. But that isn’t the case now. As time went by, web and mobile developers used different stacks for their modules. One of the modules is using Redux with Axios REST calls on the web while the mobile part is using React Apollo. Moreover, both web and mobile use their own middleware servers to communicate with backend servers. That means that every time we want to implement something we have to do the same work twice. This is hurting our development velocity. So I’ve decided to see how much work it would take to reduce the amount of duplication that we do. To do that we had to see how both web and mobile are working right now.

photo courtesy of medium.com
Feature Explanation
The feature that we had to develop overall was quite simple. we had to enable editing an order. That includes:
- Creating an Edit Order form.
- Adding a button in the order overview screen which opens the edit order form.
- Adding logic to decide when the button should be displayed.
- Finally sending updated data to the server and updating the order overview with new info.
Data Loading
One of the first issues that we had to solve was implementing logic whether the loader should be displayed when opening edit form or not. The user might open the form from other page and have all the data loaded or he might go to the form directly. That meant that with Redux we had to call multiple selectors and check that all relevant form is loaded. That form is loaded through multiple rest calls and saved into multiple reducers, so for this code, we had to write unit tests since it was complex.
const loadOneRestCall = () => async (dispatch) => { const data = await api.loadData(); dispatch(setData(data)); } ... export const loadEditOrderIfNecessary = (orderId) => async (dispatch, getState) => { if (!selectors.getComplexSelectorShouldLoad(getState(), orderId)) { return; } await Promise.all([ dispatch(loadOneRestCall()), dispatch(loadSecondRestCall()), ]).then(() => { dispatch(setEditOrderFormLoaded()); }); } EditOrder.actions.js
class EditOrderComponent extends React.PureComponent { componentDidMount() { this.props.dispatch(actions.loadEditOrderIfNecessary(this.props.orderId)); } render() { if (this.props.loading) { return <Loader/>; } return ( <Form data={this.props.data} /> ); } } const mapStateToProps = (state) => { return { data: editOrderSelector.getFormData(state), loading: editOrderSelector.isLoading(state), }; }; export const EditOrder = connect(mapStateToProps)(EditOrderComponent); EditOrder.component.jsx
With React Apollo we didn’t have to think about this at all. React Apollo made sure that if we have the necessary data in the cache it won’t make any additional network calls, but if we don’t, we can just show the loader. All of this is easy to do because data loading is written declaratively. Because this part is so trivial we didn’t have to write any unit tests for it.
const EDIT_ORDER_QUERY = gql` query EditOrderQuery(orderId: ID!) { order(id: $orderId) { ...Order } } `; class EditOrderComponent extends React.PureComponent { render() { return ( <Query query={EDIT_ORDER_QUERY} variables={{orderId: this.props.orderId}}> {(data, loading) => { if (loading) { return <Loader/>; } return ( <Form data={data}/> ); }} </Query> ); } } EditOrder.component.jsx
Calling the API
Calling the API both with Redux and React Apollo is quite simple. With Redux we emit an action which gets the data out of the store and then calls an API service. With React Apollo we write a mutation query inside the component, and then directly call it inside the component. Once again the amount of code with React Apollo is smaller but personally, we don’t like the way you have to write mutations with it. It gets quite messy when you have multiple mutations in one place. React Apollo should get hook support some time in the future, so that should simplify it.
... render() { return ( <Mutation onError={this.onError1} mutation={MUTATION1}> {(mutation1) => ( <Mutation onError={this.onError2} mutation={MUTATION2}> {(mutation2) => ( <Mutation onError={this.onError3} mutation={MUTATION3}> {(mutation3) => { return ( <View mutation1={mutation1}, mutation2={mutation2}, mutation3={mutation3}/> ); }} </Mutation> )} </Mutation> )} </Mutation> ); } render.jsx
Updating the local state
After updating the order we have to make sure that the data that we display is correct. With Redux that means updating the state inside the reducer. Once again there are some moving parts here, so to feel safe we write unit tests for it.
React Apollo takes care of it completely. Because the returned order has an ID, it will override newly returned data inside its cache. That once again is so trivial that we don’t have to write any unit tests for it (if you want to read more about React Apollo cache you can do it here).
One thing we have to mention is that with Redux, it feels easier to update the state when you create a new item inside the list. With React Apollo you would have to write a query to get the data out of the cache, then update the data and then write new data on top of that query.
const cached = readQuery(cache, {query: QUERY, variables}); writeQuery( cache, { query: QUERY, data: { orders: [order].concat(cached ? cached.orders : []), }, variables, }, ); cache.js
Usually, the code above scares new people when data becomes more complex.
E2E test
Whenever we implement a feature we always write an e2e test for it.
In mobile, we use Detox while on the web we use Puppeteer. The way tests are written is quite different, but what really matters here is how we mock our server calls.
With GraphQL we start a mock server with mocked resolvers. Because GraphQL enforces you to write correct queries, we are quite confident that the things we mock in our tests represent real-life scenarios.
it('should edit order', () => { const order1 = new orderBuilder().build(); server.setQuery({ order: () => order1, }); server.setMutation({ editOrder: (_, variables) => { return { ...order1, ...variables, }; }, }); // ...do the test, check that the view is updated with new info after calling edit order. }); test.e2e.js
If you are using REST the situation becomes more difficult. You have to mock REST calls but there is no good way to enforce that what you mock is going to be true. So even though you may have passing E2E tests you may still get production issues. Another solution is to start the whole server when running E2E tests, but that brings a lot of additional moving parts that can get flaky and writing tests becomes a lot more difficult.
Typescript and IDE support
When you are working on a bigger project Typescript becomes a really important part for it. But writing Typescript means having to write more code. And we really don’t like typing a lot. For us, the best coding experience is the one when we can have as much code completion as possible.
Both web and mobile parts of the project are using Typescript. To have proper type checking on Redux we have to write manually the types for actions and payloads, then we have mappings between actions and reducers. When you are typing action names you don’t get any auto completion. When you are creating an interface for that action type once again you don’t get any autocompletion. we think you understand where we were heading.
GraphQL, on the other hand, has type generation (Apollo Tooling). Also, both WebStorm and VS Code have plugins for GraphQL which include autocompletion for your queries. That significantly reduces the amount of manual typing you have to do. And having types allows you to easily jump around the whole project.
Redux benefits
The comparison wouldn’t be fair if we didn’t give any benefits to the Redux. In my opinion, the main Redux benefit is that we know that it works and we know that it works well. We can assume that we can easily explain to new people how Redux works. It brings a lot of boilerplate but it doesn’t hide any magic inside itself. If you want to have more flavor in Redux there are libraries that can bring it to you. There are libraries that enable less verbose code, add integrations with other libraries and many other things. Redux is just really popular and you know that if you need something you can find it.
React Apollo brings a lot of overhead. It works great while it works. But as soon as you start hitting bumps you are up for a tough time. we can expect new people to get into cases where they can get lost or React Apollo starts to break for what looks like no reason. There are quite a lot of open issues in Github. And usually, new people that we hire don’t have any experience with it.
How we are planning to reduce the amount of work
Implementing this feature twice was needed to see how much similarities and differences we have between web and mobile. Moving forward one thing that is really clear is that we need to use one middleware server instead of two. And here GraphQL is a clear winner. GraphQL allows customizing our queries for web and mobile without having to create separate endpoints for it. Also, it simplifies our e2e tests because we don’t have to start a real server anymore.
We don’t think we are going to remove Redux from the web. That would require us to waste a lot of time without implementing new things.
For more Information and to build a website using React JS, Hire React Developer from us as we give you a high-quality product by utilizing all the latest tools and advanced technology. E-mail us any clock at – hello@hkinfosoft.com or Skype us: “hkinfosoft“.To develop your custom website using React JS, please visit our technology page.
Content Source:
- medium.com