Redux DevTools Running on Twitter’s Mobile Site

Diving Deeper into Twitter’s Redux Store: Adventures in Minified Vendor Javascript

Ever since Dan Abramov showed a prototype of Redux at React Europe in 2015, I’ve been fascinated with discussions around how to better manage state in a complex web application. Shameless plug: I even open sourced Redux Bug Reporter, a drop in React component and Redux enhancer that allows for filing bugs with enough serialized state and actions to allow for complete replay of the bug. But while there are plenty of excellent discussions and posts online about Redux best practices, I’ve seen less information from how large companies are actually using Redux at scale.

About a month ago, I came across Ryan Johnson’s excellent post “Dissecting Twitter’s Redux Store”. In it, he walked through using the React Developer Tools to output the redux state of the Twitter mobile website, and broke down some of the design decisions evident from the results, mostly notably the normalized data structure.

When looking at the tweet data, he noted that in the fetchStatus data, each tweet id had a status of "loaded". Ryan stated:

I can only guess at what the other statuses are as I was never able to get a breakpoint in that let me see the status in a non-loaded state.

This made me think immediately of the excellent Redux DevTools Extension, which allows you to see all redux actions and the related state changes over time. If I could get this extension enabled on the twitter mobile site, I could see all changes to the redux store over time, and how the data responded to each action.

The process to use the Redux DevTools Extension on your own code is relatively simple, and documented very well in the project’s docs. The basic pattern is calling out to a global variable when creating a store, like so:

const store = createStore(
reducer, /* preloadedState, */
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);

If window.__REDUX_DEVTOOLS_EXTENSION__ exists, the store automatically hooks into the extension to provide the debugging capabilities we’d like.

The mobile twitter website isn’t a project I have the source code to, however. I wondered if it was reasonably possible to attach the extension in production, with twitter’s minified code. It turns out that it’s actually not too difficult 🙌.


How to Attach the Redux Devtools Extension to Twitter’s Mobile Site

  • I’ll document this using Chrome, although this can probably be done in all modern browsers.
  • Install the React Developer Tools and Redux DevTools Chrome extensions.
  • Load and log in to Twitter’s Mobile Site, and open Chrome DevTools.
  • Using the React Developer Tools, find and locate the redux store instance in the Provider component. Then, click into the dispatch function and “Pretty Print” the minified code to make it slightly more readable. See the gif below:
Locating the dispatch function
  • We’ve now found the file that most likely houses the redux package. The file is called vendor.hash.js, which gives me even more confidence that this is where third party packages such as redux are located. What we want to find now is the createStore function, which is where we’d like to inject the redux dev tools enhancer. Looking at the source code for createStore, we can see that the function starts with:
export default function createStore(reducer, preloadedState, enhancer) {
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
enhancer = preloadedState
preloadedState = undefined
}

if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
throw new Error('Expected the enhancer to be a function.')
}

return enhancer(createStore)(reducer, preloadedState)
}
  • Oh look! A human readable string: Expected the enhancer to be a function. Let’s search the vendor bundle for that string.
Finding the minified definition for Redux’s createStore function
  • One result, perfect 🎉! Let’s set a breakpoint on line 9259 (it might be a different line for you) and refresh the page. Our debugger will stop on this line, and we’ll be able to climb up the call stack to see what’s calling createStore. Again, a gif:
Using the Call Stack to find where createStore is called
  • This definitely looks like a createStore call. e(n, r, o) seems to map to createStore(reducer, preloadedState, enhancer), with enhancer being undefined. Let’s set another breakpoint here, refresh again, and try injecting Redux DevTools with o = window.__REDUX_DEVTOOLS_EXTENSION__(). Gif below:
Plugging in the Redux DevTools Extension
  • Voila! We now have working Redux DevTools. We can see all actions taken since the store was initialized, and watch in real time as more data is fetched and user inputs are responded to. Try scrolling to the bottom of the page to fetch more tweets, or favoriting a tweet, and watch the data change in the Redux DevTools.

What I Found Using Redux DevTools

I think it’s beneficial to recreate the above steps to play with the Twitter mobile site yourself, but that’s a lot to ask of someone skimming a Medium post. So, here’s a couple of interesting things I noticed:

  • It looks like all redux actions are Flux-Standard-Action compliant, having only the fieldstype, payload, meta, error. I like this practice, as knowing the general structure of all redux actions makes writing custom middleware, logging, or debugging a lot easier.
  • All network responses dispatch an action of type rweb/batch, with a payload that is an array of multiple network responses. Batching up API requests and perhaps combining them into one network request makes a lot of sense, especially on mobile, and it seems like this redux store is designed to be able to handle batched network requests easily.

Conclusions

I didn’t find anything mind-blowing in Twitter’s Redux setup, but it can be very informative to see how a complex application handles state in a manageable way. There’s a few things I would like to explicitly call out here:

  • It’s hard to hide anything that you ship down to the client. If developers want to poke around to see how you do things, usually going through a lot of effort to prevent them from doing so isn’t worth the trouble.
  • Webapps inherently support sharing of techniques. I love that this is true, because it makes it easy for developers to see how others approach problems and architect their code.
  • Minified code is not a black box. When I first started writing javascript professionally, I naively assumed that minified code was unreadable and in no way useful to a human. This is completely false. Chrome’s pretty print functionality as well as their powerful developer tools make it very possible to learn from and debug minified javascript. Granted, it’s not nearly as convenient as debugging unminified javascript, but knowing how to poke around in minified code is a very handy tool to have in your tool belt the next time you have a weird bug you can only reproduce in production.
  • The React and Redux DevTools are wonderful. Tom Occhino mentioned anecdotally at React Conf this year that he knows that a ton of react developers don’t bother installing the React DevTools. I’d like to reiterate that these extensions are wonderful and you should really consider installing them.
  • If your webapp uses redux, please consider hooking into Redux DevTools in production. This will only impact performance for users who have the extension installed and active, and since developers can figure out how to enable them anyway you might as well save them a few minutes. Reddit’s new mobile page and AirBnB already do. Also, Reddit’s mobile page goes one step further and is actually completely open source and worth a look.

If you made it this far, I’d say it’s fair to assume that managing global state in complex apps is something that interests you. I work at Slack (and love it), and have been thinking a lot recently about how to better manage global state while still supporting existing code and allowing for new features to be developed at the same time. If you are also interested in making large structural changes to an application that tons of people use every single day, we’re hiring!