Isn’t our code just the *BEST* 🙄
I just completely rewrote the Bumpers web app using react. (If you don’t know what bumpers is, it’s this super chill app for recording/sharing audio stories on your phone. Download it, it will actually f*** your whole life up. It’s the greatest app ever made. React? It’s aight.)
At any rate, what follows are all of my notes, thoughts, etc. on this process. (Things I wish I had read before I started). Hopefully you’ll get something out of it.
GOD. I hate frameworks. A lot.
I also hate not having a framework, and anyone who “rolls” their own “framework”. I also just hate coding, in general. And most of all I hate writing about code.
So bear with me.
Lately my coding style has been some sort of sociopathic, oscillation between fits of crippling self doubt and an extreme kanye-like god complex — where I’m either marching around my apt alone all day crying aloud or I’m calling my mother to let her know her 30 year old son is “f***ing the game up (in a good way)”. So naturally it seemed like a good time to take a break and write about it.
A little history: The bumpers “website” was so nice. There were about 7 es6 classes, no external dependencies, and only about 759 lines of code. Total.
Meanwhile, Nicolas Gallagher was part of the team that had just completed a year long project rewriting Twitter’s mobile-web product in React.
I’ve known Nicolas for a long time. And he is easily one of the more thoughtful people I know. So when after this, he told me that React had essentially solved all the problems in the Front End Development space and that he had moved on to worry about other things, I told him to f*** right off.
At face value, React had these things going for it:
- endorsed by friends smarter then me like Nicolas Gallagher, Alex MacCaw, Guillermo Rauch
- client-side rendering (good for audio apps, so you can persist playback across navigations)
- thoughtful component model
- people were moving away from (or at least challenging) CSS
- facebook nerds wrote it
- production apps ~like instagram, twitter, etc.~ were using it
- people seemed to be finally settling around a data paradigm in redux (and liking it)
But at the same time, react had a number of things I wasn’t excited about:
- production server-side rendering requires a node server (and even then solutions seemed half baked)
- styling practices are super fragmented across community (do you use aphrodite, css-modules, style tags, etc. — what about your dependencies?)
- facebook nerds wrote it
- webpack → babel → jsx → hot loading → source maps → chrome tools as a stack crippled my poor little macbook
- I had to watch these f***ing “egghead” videos to learn redux
- tooling seemed disjointed and over the top…
Despite all this, we decided to go for it. (The main hope being react would somehow set us up to build something which felt more “app-y”).
Choosing “the rest”
Turns out that after you decide on react (your view lib), you’re really left with a handful of other decisions: How are you going to manage state? How are you going to style your components? Are you going to use es6? es7? es2015? jsx? What do those even mean? Are you going to use webpack? or browserify? Where is everything going to live? …
I started by mashing together TJ Holowaychuk’s boilerplate repo (https://github.com/tj/frontend-boilerplate/tree/master/client) (which he admits to basically being obsolete in the readme) and this long email Nicolas had penned to me about where twitter had landed (half of which I didn’t understand at the time, but regardless, you can read the email in its entirety here: https://gist.github.com/fat/9ab5325ab39acfe242bc7849eb9512c4).
I also looked at a few of the many “universal-react-redux-glahblbhalkd” boilerplate repos on github, but they largely all gave me panic attacks.
At any rate, somehow I managed to get to a place I’m somewhat happy with, which looks like:
- Babel (with “presets”: [“es2015”, “stage-0”, “react”]) So i can use all the crazy new shit like spread operators, arrow functions, etc.
- Webpack with hot loaders, which (when it was working) I found handy when updating style at particular app states. But definitely caused me lots of anxiety. Honestly, I feel like no one truly understands how webpack works. And we all just keep throwing random properties and plugins at it praying that it will all turn out. AggressiveMergingPlugin? sure. OccurrenceOrderPlugin? ok. DedupePlugin? fine.
- Redux coupled with normilzr and denormalizr to help with destructuring and then rehydrating api responses.
- Aphrodite/no-important js styles, not css, but without all those !important everywhere.
- Svg-react-loader which loads svg’s as react components inline.
- A handful of others if you see something else in that dependency list you’re curious about, leave a note and I’ll explain it.
OKAY. Once I settled on the 38 dependencies bumpers.fm the website hadn’t required, it was time to write some actual code. 💪
Our directory structure is organized around two entry points:
- index.js which instantiates the router and store for our core app bundle.
- embed.js which is responsible for our smaller embed bundle (as seen in slack, twitter, medium, etc).
From there we pull in our routes from the aptly named “route” directory, which is currently just a simple, single react-router component that looks like this:
Notice these routes point to what we call ✨screen containers✨.
In Bumpers our react components are actually split into 3 different directories, depending on their function (4 if you include the routes directory). This way of organizing components was basically just stolen directly from Twitter, who in turn I think borrowed it from Facebook and loads of other projects. It looks like:
- components this is where our functional ui components live
- containers this is where action handlers for our ui components live
- screens technically these are just containers — but typically do more top level page fetching, and are less concerned with action handling.
SIDE NOTE I actually started out with just a containers directory, no “screens” (which is pretty common from what I’ve seen around the react community). I moved away from this on Nicolas’s recommendation and because seeing a bunch of “screen” suffixed files mixed in with my non “screen” suffixed files bothered the hell out of me.
The last two directories are the “store” directory and the “constants” directory. The “store” contains all of our redux logic like actions, reducers, selectors, api endpoints, etc. (which I’ll go into more depth on below), while the “constants” directory contains…well… constants.
Our UI components are pretty standard, functional, stateless, presentational, reactive components. Here’s a standard episode component (which is made up of many other smaller, standard, functional, stateless, presentational, reactive components).
As I mentioned above we use Khan Academy’s Aphrodite to generate our css.
QUICK NOTE Originally, I wrote the app using the style-loader package, but its inability to provide a convincing server strategy (something I eventually want to explore), was enough for me to try something else. (I also routinely considered React-Native, which Nicolas would constantly remind me was better then whatever solution I had independently arrived at because he had written it).
I was able to achieve a similar style to what we did back when I worked at Medium, creating type scales, color scales, zIndex scales, etc. And was even able to use the ES6 computed property names feature to abstract my media queries into variables.
One last thing I’ll quickly mention is all our relevant files live in their respective component directories.
Styles are placed in a separate file named style.js, alongside relevant svg assets which are imported directly using svg-react-loader. Doing this makes it super easy to delete components / features, and not be constantly left asking yourself: wait, do i still need this css? do i still need this svg?
Honestly, I’m not going to say much of anything about containers™. We’re doing nothing special here beyond separating the screen /container directories (which I already covered above).
I did however draw another picture for you (wow, right over there) because I felt bad for not having much to say about containers 😬. And i thought this was a good time for you to take a break maybe. Stretch?
~ALRIGHT~. This store section could easily be its 👏 OWN 👏 ENTIRE 👏 ARTICLE 👏, but I’m going to try to slog through it for y’all… so bear with me. Also fair warning — it’s about to get DENSE.
SIDE NOTE what follows is probably going to make absolutely zero sense at all unless you’re familiar with redux (http://redux.js.org/). If you’re interested in learning more about Redux and using it to manage state in your react apps — I recommend checking out these egghead tutorials, they’re free and all considering pretty good: https://egghead.io/courses/getting-started-with-redux
Our store is made up of 4 top level files (I go into more detail on each below, but just real quick)…
- index.js — our store initializer
- reducer.js — pulls all the reducers from different objects into one giant “combineReducers” method
- schema.js — all our normalizr models
- api.js — an api helper for our store
Beyond this, our store is structured around models, with directories like users, prompts, etc. — rather than the traditional redux top level functional directory hierarchy of actions/, reducers/, selectors/, bleh.
Of course we still have the traditional separation of actions, reducers, etc. that redux requires— but this is done on the file level now, nested within its model directory (take a look at the expanded user folder in the image to the left for an illustration of what I’m trying to say).
OKAY, but why tho? In building this app I found myself constantly saying things like: “dang i rly want to work on user stuff rn” and almost never saying something like: “dang, i rly want to change a bunch of reducers all at once, sure am glad they are all in this massive f***ing reducers directory”.
SIDE NOTE I can’t remember where I actually first saw this strategy… but I’m confident I didn’t invent it. If you know someone who did, or who explains it well, leave a note and I’d be happy to point people to that. Also i ~think~ twitter does something similar. But i could be making that up.
Nitty gritty of the root level files
Alright, so the store’s index.js (briefly mentioned above) is responsible for 3 main tasks:
- Importing pre-fetched, embedded data into our redux store and setting the store’s initial state (Our backend prefetches data when a user accesses something like bumpers.fm/fat so that when the react app loads, it doesn’t have to immediately make an xhr request for user data, and instead it can just quickly populate the page).
- initializing our redux store with our root reducers.
- applying middleware like thunk, react router’s browserhistory, devtools, and more…
In practice, this all ended up looking something like the method below — but for whatever reason caused me a lot of grief:
Next let’s briefly visit our reducers.js file, which is essentially just a single
combineReducers method which pulls in reducers from our other directories and exposes them as a single giant reducer waterfall thingy. tbqh, this file is pretty boring and i could probably have just put it into index.js 🐴. whoops.
However! One thing that is worth calling out here is that our “entities” reducer (seen above) operates like our store’s cache.
To pull this off we used a project called normalizr (https://github.com/paularmstrong/normalizr) to coerce our deeply nested JSON api responses into more manageable/cacheable ID-indexed objects. Which is to say, we start with a more traditional api response and then transform it into a saucier, ID-indexed entity hash:
As you might imagine, this cache technique is ~super useful~ as you start navigating around a react app — inasmuch as if you fetch an episode you probably have already fetched a user (as an author), which you can now lookup by ID using one of the selector methods, without having to hit your backend (read: almost instantaneous navigations. wow).
Our schema.js then is where we specify the logic for pulling off the above entity transforms for our cache (and for normalizr). These relationship mappings end up being pretty simple to write — but definitely easy to forget. If you’re going to go the redux cache route, it’s def worth giving these a look.
SIDE NOTE Not pictured above, Schema.js also contains a custom mergeStrategy that we wrote special for bumpers. For whatever reason, the default mergeStrategy provided by normalizr was tripping all over itself, but I’m not going to get into that here because it was almost certainly user error 😅. (That said, if you are experiencing similar problems, leave a note and I’m happy to share where we landed.)
Our last root file in the store directory is api.js.
After a lot of banging my head, I noticed that the thunk middleware (which we relied on for async actions) allows us to pass an extra argument to all of your redux actions (on top of dispatch and getState).
This is incredibly powerful, and I ended up using it to pass a global api helper into all of our actions. This api helper (defined in api.js) provides quick access to all our api endpoints, with additional helpers for JSON parsing, error checking, and more. You’ll see this in action below… when we get into the… action… files…
Our redux reducers evolved to have 3 main functions.
- Define an initial state
- Define a preloadData handler (for our embedded data)
- Expose action handler reducers
Our initial state often looks something like this, with status constants for request state and active id’s:
Our preload handlers take our raw data objects, and unpack the data entities, in this case setting a default active user:
And a typical reducer looks something like this (note the use of computed property names (Es2015). We pull these in directly from the action definitions, covered below).
There’s some magical things happening inside our actions files. First we use “redux-actions” createActions method to define our action names:
We do this so that in our reducers file we can use computed property names (mentioned earlier) to only have our action names defined in one place. Also take a look at the way we name our actions:
method + object + property. This is ~super~ important for keeping all your reducer keys readable and unique. I’ve seen a lot of examples around the web of people using lazy, generic names like “username” or “setUsername” for keys… trust, you’re going to have a real bad time if you do that (remember, keys are global and bugs caused by naming conflicts are a major pita to track down).
For async actions we use redux thunk and the api helper we mentioned above. This helps keep our async methods super tight and focused.
In the example above, we set
isFetching on the user object, fire a request off to our api, check the response for an error status code, set our jwt token, convert the response to json, normalize the response using normalizr (for caching), and then set the active user state.
This is the cleanest way of handling async methods in redux i’ve ever seen (don’t @ me).
I haven’t seen anyone else doing these endpoint files — but I find it a really clean way to keep your relevant api calls all living in one place (not to mention makes stubbing tests super easy). Also note the “isomorphic-fetch” — i swear someday we’ll render this stuff on the server 😤. Meanwhile, the cool thing about using fetch is it returns a promise, and makes for a pretty clean api when pulled into our async actions.
Lastly, our selectors file uses the denormalizr library (https://github.com/gpbl/denormalizr) (normalizr’s sister project) to re-construct more workable data from our cache. It basically just uses the name models to reconstruct a big nested object — you don’t ~have~ to do this, but i found it much more enjoyable/predictable to work with data in this way.
Other than that, our selector methods look pretty much how you would expect:
WOW. Alright y’all that felt like a serious journey. And that store stuff was probably way too boring and lost like 90% of readers, so i’m sorry.
Thanks so much for reading, and sorry if this post was insufferable. I just promised myself i would publish something like this because i found learning all this shit to be so insanely scattered/hard.
If you have any questions about anything, leave a comment or note and i’ll do my best to answer.
Ya, I’m definitely happy! I would be lying if i didn’t say it was a major pita, but Bumpers is basically just a massive audio player app — and managing state across navigations and throughout the many little feedback elements we have all over would have been insanely hard otherwise.
I think there is also something to be said about using “familiar” tools when you have them — and I’m hoping if we ever get to hire more frontend’s at Bumpers, that they’ll be able to dive in fairly easily without feeling totally overwhelmed (and like they need to learn everything from scratch).
Yep, pretty much. We did a similar thing at Medium while i was there also. You have to be careful with how you do it, because of script injection hacks, but it’s a pretty cool way to approach something like the “server side rendering” feel, without having to render react templates on the server.