8 Tips for Dealing With Large React Codebases
(X-posted to Medium)
Use Guard Clauses Liberally
As the lines of code grows, so do the opportunities for your application to break. It’s important to be defensive about this from day one. One cool trick from the old ages of programming is the guard clause.
When the render
function in React returns null
, the component simply doesn’t render. In this example, usage of guard clause will avoid errors down the line if anything else in render
depends on this.props.user
being set to something other than undefined
, like a user object.
Ideally, there would be checking higher up in the component hierarchy, but often times we don’t live in an ideal world. Using guard clauses to bail early if variables are unexpected or set incorrectly is a great way to avoid your users experiencing errors.
Organize Component Functions
React components generally have two flavors of functions: built-in component life-cycle functions and functions you write yourself. Make sure you have a clear and consistent way of organizing these functions in the code since that will make reading (and fixing) your codebase easier.
On a non-dumb component, I typically like to put the constructor
at the very top (after the class declaration). Then I’ll add any component life-cycle functions (except for render
)after the constructor in alphabetical order. After that, I’ll prefix the non-lifecycle functions I write myself, like change handlers, with a _
. Those are also arranged in alphabetical order. Finally, the component .es6
file will end with the render
function, since that’s the first place I’ll look if there is a bug.
You can organize your component’s functions any way you choose, but make sure it’s consistent. Having a standard organization and file structure makes your codebase easier to read and grok.
Know what super() does
In a constructor, super()
is one of those magic incantations that just makes everything work. But it’s important to know what super does and where it comes from.
In a nutshell, using super()
makes the this keyword accessible in your constructor and super(props)
makes this.props
accessible in your constructor.
Beware of Gotchas when assigning state from props
In a well-organized codebase, the values of state
and props
should be independent from each other. But you can’t control everything, and sometimes you’ll find yourself in a situation like this:
Where can we go wrong here? Note how state only gets assigned once when the component is created. What happens to this component’s state when the foo or bar props are updated in the parent component? Nothing. If you’re assigning values in state from the values in props, you must listen for prop changes and update state accordingly.
9 times out of 10, the lifecycle method componentWillReceiveProps
must also be used in your component to prevent state and props from falling out of sync.
This, of course, comes with its own gotcha — receive is a hard word to spell.
Use Verbose Component Names
As developers, we have a limited number of keystrokes we can make before we die. While it might seem pragmatic to save those keystrokes for writing actual code instead of long component names, this approach reveals itself to be short sighted when trying to refactor your code two months later. It’s easy to imagine multiple development and feedback cycles in which the name of a component changes from Nav to SideNav to DashboardSideNav to DashboardLoggedInSideNav. Why not begin your development with the most verbose component name possible and save time refactoring names in the future? Furthermore, using verbose component names forces you to come up with a common vocabulary of page locations and their associated widgets. This common vocabulary can be shared between the CSS and JS ,making your codebase more readable. And verbose, explicit component names make it much easier to jump into a codebase and start modifying things by reducing the time spent tracking down the source files from the compiled frontend output.
A variable named _
Don’t waste time thinking about names for one-off variables. Decide on a common name for variables that are simply holding data to be transformed down the line.
Not only do you save yourself some mental cycles thinking of a variable name, you indicate to other developers that the _ variable is just a one-off variable and doesn’t have that much importance. Some languages, such as Go, have this idea built in.
One cool trick for change handlers
When it comes to controlled form components, it can be burdensome to write a change handler for every from input. Luckily, by assigning a name attribute to the input we want to change, we can create a very generic change handler.
When the change event fires, the name
attribute is accessible on the event’s target, making it easy to assign the right property in state
based on which target the event came from.
Have a Readme
This is probably the most important tip here. A readme is a single document that describes the application. Having a boilerplate or empty readme is sloppy development. A good readme is directed at fellow developers. It contains instructions on how to stand up the application the first time, documentation of the deployment process, and architectural notes. Include copy and paste commands for commonly run operations. Include the steps of the code lifecycle: how a commit makes it into production. It’s perfectly OK for a readme to cover a wide range of tangentially related topics (kind of like this post). The readme should be one of the first introductions to a new and mysterious application and should seek to explain as much as it can.