5 Common React Mistakes and Remedies

Introduction

At 10,000ft, we’re all in on React and Redux. We considered frameworks in the process of migrating our app from our previous architecture to React, and were immediately drawn to React/Redux for its performance, robust ecosystem, and predictable and testable state management.

But in my personal effort to ramp up my productivity in the React and Redux ecosystems, I’ve made small mistakes that have had extremely confusing results. The fixes for these errors, while typically only a few characters, weren’t immediately obvious. Some error messages were difficult to google. Others led down rabbit holes of reading 100+ message threads on the React repository on GitHub.

I’ve organized some of these easily avoidable mistakes here, to give anyone else running into these or similar issues some potential solutions.

1. Failed prop type: [FooComponent]: prop type ‘bar’ is invalid; it must be a function, usually from React.

One thing I appreciate about React is the effort they put into their error messages. This is a practice that Dan Abramov has adopted with Redux to great effect. (Uncaught Error: Actions may not have an undefined "type" property.
Have you misspelled a constant? is one of my favorite error messages.)

While these error messages are well meaning, they don’t always lead to a fix. In this case, I found this error because of this piece of code, defining the PropTypes for a component:

// WRONG
DatePicker.propTypes = {
  onCustomDateRangeSelect: PropTypes.func.required,
  activeDay: PropTypes.instanceOf(Date).required,
  initialMonth: PropTypes.instanceOf(Date).required,
  filterId: PropTypes.string.required,
  customDateRangeStart: PropTypes.required,
  customDateRangeEnd: PropTypes.required,
};

You might have already identified the issue if you’ve been working in React for some time. But even experienced users can easily make this mistake; I ran into this error after working full-time in React for over four months.

What made this confusing for me was that I got this error on the onCustomDateRangeSelect property of the propTypes object, and in the code, it clearly was a function. I started thinking about what could cause the React internals not to recognize a function as a function, and from there, I began devolving into wild conjectures about the v8 internals.

The issue here is nothing that complex; it’s simply that I’ve written required instead of isRequired on the PropTypes declarations. So, the fix was to change the above block of code to read:

// RIGHT
DatePicker.propTypes = {
  onCustomDateRangeSelect: PropTypes.func.isRequired,
  activeDay: PropTypes.instanceOf(Date).isRequired,
  initialMonth: PropTypes.instanceOf(Date).isRequired,
  filterId: PropTypes.string.isRequired,
  customDateRangeStart: PropTypes.string.isRequired,
  customDateRangeEnd: PropTypes.string.isRequired,
};

2. React.createElement: type should not be null, undefined, boolean, or number. It should be a string (for DOM elements) or a ReactClass (for composite components). Check the render method of SomeComponent.

This is an error I commonly get because of the nature of my workflow. We have a convention in our React codebase that there should be a 1:1 component-to-file ratio. In other words, we try to have only a single component per file. From a practical standpoint, this makes working with file management in our text editors easier, and from a philosophical standpoint, this discourages tight coupling between components. I’ll often work on components in the same file to get them working together before breaking them out into a separate file. And when I do break them into separate files, if I’ve made the component sufficiently isolated, it’s a simple copy-paste and import SomeComponent from './SomeComponent' away.

But there’s a really key line missing in the file where both components are colocated that won’t be present when the component is pasted into another file:

export default SomeComponent;

Without that default export, the component itself doesn’t become available to the ES6 module loader, so its type is effectively null, as far as React is concerned. The error is misleading, however, suggesting to look at the rendermethod of the component, which I know to work without error, given that I had it working when both components lived in the same file. And if I’m working quickly, it’s easy for me to forget the export statement. Thus, that simple export default statement at the end of the file fixes this perplexing error.

3. Redux actions dispatch, reducer doesn’t respond

This issue requires more explanation than the others so far, and has one of the most mystifying consequences. Recently, I was debugging an issue where clicking on a button appeared to fire an action that caused a Redux reducer to respond to an unrelated action. If you’ve ever debugged a piece of code that appeared to hit both paths of a mutually exclusive if/else statement, you understand my sense of incredulity when this occurred.

One common pattern we use is colocating the Redux action type constants with the corresponding actions. With this pattern, it’s easier to have fewer files open in your text editor when you’re working with actions, and it’s easier to see where the constants are used.

When I’m working quickly, however, I try to save keystrokes, which sometimes causes more work in the long run. One tempting way to try to economize typing is to copy-paste constants when only one part of the string changes. Consider the following Redux action code:

export const REQUEST_RESULTS = 'REQUEST_RESULTS';
export const requestResults = () => ({ type: REQUEST_RESULTS });
export const RECEIVE_RESULTS = 'REQUEST_RESULTS'; // Constant name and string don't match!
export const receiveResults = data => ({ type: RECEIVE_RESULTS, data });

Did you spot the error? That’s right, I’ve set RECEIVE_RESULTS equal to the string 'REQUEST_RESULTS'. Unless I’m careful, it’s easy to copy the line with REQUEST_RESULTS and accidentally only change the variable name.

In this case, if you have a thunk that, on page load, makes an async request, it’s likely to dispatch the REQUEST_RESULTS action. This is helpful for setting the page to a loading state, for example. When it receives the results through that thunk, the action creator will then dispatch RECEIVE_RESULTS in the hopes that the reducer will take the data and pass it to the concerned components for rendering.

However, because I set RECEIVE_RESULTS to the string REQUEST_RESULTS, the reducer will hear that action, and set loading to true, which is the state we already have. The UI won’t update, and I’m left scratching my head.

So, next time your reducer seems to fail silently, make sure your constants are imported and used correctly.

4. Redux actions dispatch, reducer doesn’t respond (pt. 2)

This error has a similar effect to the previous one, but is caused by a different mistake that’s worthwhile to point out.

Generally speaking, one of the things we love most about Redux is how predictable it makes state management. The data flow from the actions to the reducers and through the components is very straightforward.

Until is isn’t.

When you’re looking at an action you’ve dispatched, and the reducer alongside it that’s supposed to respond to that action, but the state is not changing, it can be quite confusing. Here’s a situation I’ve found myself in recently:

// actions/results.js
export const FETCH_RESULTS = 'FETCH_RESULTS';
const fetchResults = () => (
  {
    type: FETCH_RESULTS
  }
);
// reducers/results.js
import { FETCH_RESULTS } from '../actions/result.js'; // Wrong filename!
const results = (state = initialState, action) => {
  switch (action.type) {
    case FETCH_RESULTS: {
      return {
        ...state,
        loading: true
      };
    }
  }
};

This code looks pretty standard for anyone with a few months of Redux experience under their belt. However, notice that I’ve imported FETCH_RESULTS from '../actions/result.js, but I’ve named the action file results.js. Now, when I fire the action, the reducer won’t respond, because FETCH_RESULTS is essentially undefined.

When you have the action creator and the reducer side-by-side, with console.log statements in each, it can be maddening to believe that the action disappears between the action creator and the reducer like Harry Potter donning his invisibility cloak.

The most frustrating thing about this issue is that it fails silently. Unlike some of the other issues mentioned in this post, that are accompanied by big red error messages in the console, nary an error accompanies this mixup.

Let’s face it: managing string constants can be awkward and unwieldy. Unsurprisingly, a lot of developers have been frustrated with the implementation of action types as string constants for this very reason. Next time an action goes missing, make sure you’ve imported the constant from the correct file.

5. TypeError: (0 , _someModule2.default) is not a function(…)_

If you’ve never seen this error, count yourself among the luckiest developers in the field. One of the first things you’ll notice is that the _someModule2 is the result of babel compilation, so it’s not even the correct module name. In this example, my module was called filters, and the TypeError pointed to _filters2.default, which was pretty confusing.

Understanding why this error occurs requires a decent understanding of JavaScript module loading and ES6 destructuring assignment. In short, if you have a JavaScript module with multiple named exports functions, it’s important to remember to import a function with the curly braces:

// WRONG
import calculateTotals from '../utils';
// RIGHT
import { calculateTotals } from '../utils';

If you’re adept at ES6, you’ll immediately realize that the top version will only work if utils exports calculateTotals as the default export. Otherwise, if it exports several functions, you’ll need to use destructuring to pull them out individually.

Even if you’ve been working with ES6 for some time, this is an easy mistake to make if you’re thinking about syntax more than the JS engine implementations. Especially because the “wrong” version compiles! But if you dig more into module loading and destructuring assignment and deepen your knowledge of the underlying implementations of each of these import statements, you can avoid making this error in the future.

Conclusion

As a developer, you must be detail oriented, taking great care to import constants from the correct files, assign variables to their proper values, and know the difference between required and isRequired. But you’re also looking at screens for several hours a day and switching between dozens of files in two to three repositories that span at least three languages (and different versions of those languages, to boot!).

When you’re managing all that while attempting to build features with a velocity that adds value to your company, it’s easy to make a small typo with disastrous consequences. And while these typos are easy to spot when you’re just looking at a snippet of a few lines of code, they become much harder to see when you have several files open!

My hope in writing this post is both to normalize the fact that we all make mistakes (even stupid ones like these), and to add a bit of help to developers like myself who occasionally find themselves feeling shame for googling things like “redux action disappears between action creator and reducer.”

Nick Cox
December 14th, 2017
Receive expert insights, tips + tricks every month
RELATED ARTICLES
Get our newsletter