React in Retrospective @QuizUp

Arnar Þór Sveinsson
QuizUp Blog
Published in
8 min readMar 31, 2016

At QuizUp we started using React in January 2014. We had a small project called the Editor to manage topics and questions in the app. At one point a coworker dropped the following gem about this project:

“This project is amazing, it uses all the web tech that has ever been released. It is a Python Flask project with React views and backbone models written in Coffeescript, using both bower and npm and has stylus for CSS”. — Kristján Oddsson

Some might say, this is horrible, and they’re right, but this still works like a charm.

Bringing QuizUp to the web

When we started working on QuizUp 2.0 we realized that we wanted to bring all the features of QuizUp to the web. From our previous experience with React in the Editor we knew that we wanted to use React for QuizUp for web. We wanted to change our setup a bit from how we did it in the Editor.

QuizUp.com is huge React project.

We started working on this project in October 2014 and today QuizUp.com is a huge project. It has ~30.000 lines of JavaScript, ~20.000 lines of CSS, 40+ data stores and 1200+ unique user action types. You can chat, post, read posts from others, create your own unique QuizUp topic and of course enjoy a game of QuizUp.

We started QuizUp.com around the time that Flux was gaining momentum, so we decided to give it a try. At the time this was still just a concept Facebook had described and there weren’t any community implementations yet. We implemented Flux based on suggestions from Facebook’s Flux article. We had an event emitter (dispatcher) with actions, APIs and stores which could run both on the client and the server to do server side rendering. Our implementation served us well, and Flux with a pure React stack made us much more productive than in the patchwork days of dealing with the Editor..

With a large web app you need SEO, fast initial load and fetching the most critical data on the server inside the datacenter. Thus we decided to implement server-side rendering. Configuring server-side rendering turns out to be more difficult than it looks. The good thing is when it’s up and running you won’t need to work on it again.

Culture of testing

With Flux, testing this app was fairly simple. We just dispatch all actions needed for the initial state and then dispatch the actual test action.

Our components are very simple so we didn’t find the need to test them. Their only purpose is to accept data, and render it. We try to keep all our state in the stores, which were thoroughly tested. This prevents the components from needing local state.

CSS

Another thing we realized when writing such a huge project was the need for a CSS style guide. The main selling point of CSS is it’s cascading behaviour and global styles. These attributes are a terrible thing to have in a huge project like QuizUp.com. We decided our style of CSS would use the BEM setup. This helped us in both performance and preventing bugs in our CSS. The downside of BEM is the hardest problem in computer science — naming things. At the end of the day you’re using more characters for your class names than for your actual application code.

BEM styles can get really long

Downsides

Writing QuizUp.com was enjoyable but we also had some problems along the way.

One of the issues we had was regarding performance of the app. We wanted to skip passing all actions through our whole app to be able to do API calls from anywhere. We created a higher order component to forward our dispatcher through the DOM tree. This seems like a fine thing to do, and it most likely works for small apps. For an app the size of ours, this turned out to be a major performance problem. Even though no re-rendering takes place, it will need to check every component if it should re-render, which is slow for big apps. Our solution to this was to revert and always pass the data down the whole tree.

Backend server

The server behind QuizUp.com is very simple. It’s only purpose is to render React to HTML, serve static assets and proxy our requests to our APIs. Even though it’s simple, we’ve managed to break QuizUp.com multiple times by accident. All these incidents happened because of the hidden complexities of server-side rendered React. Because of differences in development and production environment, bugs can surface only in production. Debugging these incidents is a tedious task. It requires building the project locally and start a production build. This takes the iteration loop from a matter of seconds to around 10–15 minutes.

Thinking back, we’ve done an amazing job with this product. We managed to set up a full blown social network, with the QuizUp gameplay in just over 6 months.

Make internal tools great again

A lot has changed in the year after we started working on QuizUp.com. Due to a shift in the company’s focus, adding new features — such as in app purchases and a single player mode, we needed new and better in-house tools to manage our community.

This was a perfect opportunity to move the web-stack forward, building on our experience from previous projects. We’re not afraid of trying new things and we care about our developers productivity. A logical next step for us was to switch from our home-made Flux implementation to Redux.

We started working on our new internal tool called Jarvis in October 2015. It may be too early to say if we’ve made the right decisions in the changes we made but we certainly feel like it.

Redux

In Redux there’s only one store for the state of the app. This store is updated through actions using reducers. In Flux there are multiple stores which can have dependencies internally as well. This small difference makes it easier to reason about the state using Redux.

Flux vs Redux

Like I mentioned above, developing QuizUp.com was a really nice experience at the time. After experiencing Redux we don’t like our home-made Flux implementation as much. Redux has enabled us to move much faster than we used to before.

The Redux dev tools are a major key in development productivity. Say goodbye to console.log since it’s never been as transparent what the app state looks like.

Testing reducers are the purest unit tests we’ve come across. Testing is as simple as calling the reducer with the initial state and action, and asserting the return value.

CSS Modules

Another change we made to switch from our beloved CSS BEM setup and start using CSS modules instead. CSS modules enable us to stop thinking about the setup of each individual class name and it’s scope. Instead we can start focusing on CSS and markup.

Poor man’s server side rendering

Our arch enemy, the server side rendering, was killed in this project. We decided to go with what we call “Poor man’s server side rendering” instead. We fetch all the data the site needs on the server and insert it into the HTML file as we serve it out to the client. This is a great performance boost when rendering the site initially. We already have most of the data we need in the payload so we can partially load the site. Client side we read the payload from the HTML and then set it as the initial state of our reducers. After that we do the route matching to fetch all route specific data. By doing this we unify our development and production environment. At the same time we eliminate production specific bugs as well as reducing load on our servers.

typeof NaN == number

When we started working on Jarvis we wanted to try out Flow. The web-tools team has both frontend engineers and full stack engineers. QuizUp is gradually moving to Java and Scala for all it’s API services. We wanted type safety and unified infrastructure and these languages fit the bill. However, JavaScript doesn’t have type safety.

Typed JavaScript code

We have yet to master Flow but we feel the API for it is far from complete. We love type safety and Flow has already found bugs in our code where internal libraries weren’t used properly. Our biggest grief with it is that it doesn’t handle some cases at all. Decorators are still completely unsupported. That is inconvenient when using Redux-Connect to connect your components to the reducers. Another quirk we’ve seen is when using libraries that don’t support Flow. It doesn’t help us in any way and can even cause Flow bugs because it doesn’t know the code in use. Despite its flaws we find this really promising and are looking forward to see it grow in the community.

Mutability is the root of all evil

Lastly we want to mention ImmutableJS. It has been crucial to our work. There shouldn’t be any code updating your state except actions, but mistakes can happen. Having the state immutable can help preventing these bugs when sharing state between components. Working with React it can also be a huge performance boost to re-render checks. Deep equality checks for big objects are expensive, while equality checks for Immutable objects is a cheap operation.

Looking back

Our first version of writing React was definitely not the best way to do so. For a new employee to take a look at this code it looks like a monster. As we’ve progressed we’ve created a much better infrastructure around our React projects. Today it takes less than a week for new employees to open our web projects and start opening pull requests.

The great thing about the Editor though is that it gave us a simple introduction to how React works. We didn’t go all-in with Flux, Babel (which didn’t exist at the time) and the tools that are emerging today.

There’s been a lot of talk about JavaScript fatigue recently because of tooling. In the Editor we tried having no tooling and progressed as we moved into newer projects. Our opinion is that tooling is nice, not necessary. Having development tools can increase your productivity if they’re used when needed. We’ve experienced that these tools should not be added all at once, and don’t add them until needed. Adding them all at once will make it hard to understand what’s going on when unclear bugs occur.

We’re constantly working on improving our work and developer experience. We look forward to seeing what the JavaScript community will bring next.

If you have any questions or just want to chat feel free to reach out on Twitter @arnarths @solviloga.

--

--