React Do’s and Don’ts
I have been using React for over 2 years now and over this period of time I have picked up some general patterns that you should try and follow and others that you should try and avoid.
I have found these though various investigation (often late at night), blog posts, code review and other times while brainstorming with colleagues.
Ill start off with the Don’t and then follow up with the Do 😀
Note: there is already blog articles that go into lots of detail about some of these topics, this is designed to be a quick reference.
1. Don’t duplicate source of truth — props in initial state is an anti pattern
constructor(props){
super(props);
this.state = {
mrData: props.mrData,
};
}This leads to the duplication of the source of truth — is it state or is it props? If you pass in new props.mrData it wont be used because its only invoked when the component is first created — DON’T do this.
If you do need to set state from props — clearly mark the prop name so its clear that its only used as seed data:
constructor(props){
super(props);
this.state = {
mrData: props.initialMrData,
};
}2. Don’t bypass the React event system unnecessarily
Try and avoid bypassing the React event system (unless you really need to).
Take the example below — where we need to handle a click event for the whole page:
class Hello extends React.Component { componentDidMount() {
document.addEventListener(‘click’, this.onClick);
}
onClick = () => {
alert(‘Clicked!’);
}
render() {
return (
<div>
<h1>Hello {this.props.name}</h1>
</div>
);
}
}ReactDOM.render(
<div>
<Hello name=”World” />
</div>,
document.getElementById(‘container’)
);
On mount of the Hello component it is registering the click event onClick so when you click on any part of the page — it will alert “Clicked!”. Unfortunately this will not work on ios touch devices — so DON’T do this (unless have a relevant use case to do so). You can get it work on ios devices by adding touch events but you're just giving yourself more work unnecessarily.
By registering the event via document.addEventListener you are registering the event outside of React. You can only register React events that are on react components (eg: <div onClick={this.onClick} />)
React normalizes events so that they have consistent properties across different browsers — this is a good thing (React creates SyntheticEvent, a cross-browser wrapper around the browser's native event).
So a way to rewrite the above could be to have a parent component that wraps the whole page and has a React onClick event which handles any actions for the whole page. This would then have a consistent onClick event across all browsers / devices — taking advantage of Reacts Synthetic events.
3. Don’t unnecessarily use context or have your application tied to it
Context is an experimental API (update: there is an updated api on React 16 so this point is out of date if you are using React 16) — so its likely to break in a later version of React. It also has limitations such as not being able to work correctly with shouldComponentUpdate (which only works with state and props)
Firstly if you want to use context please consider this breakdown by Dan Abramov before continuing:

If you decide you want to use it, take into account these 2 points:
- Only use it when components only need the context data once — because otherwise components that use context data and have a
shouldComponentUpdatewill not render with the latest context data. If you really need to do this, use something like MobX with react or use https://github.com/ReactTraining/react-broadcast - Try to isolate the use of context so that if you need to rework your code later on, you can much more easily.
I find the best way to isolate context is to create a provider to pass data through the component tree, eg (similar example to react docs, which calculates if we are in desktop or mobile mode):
import React from 'react';
import PropTypes from 'prop-types';class MediaQueryProvider extends React.Component {
constructor(props) {
super(props);
this.state = { type: 'desktop' };
} getChildContext() {
return { type: this.state.type };
} componentDidMount() {
const checkMediaQuery = () => {
const type = window.matchMedia('(min-width: 1025px)').matches ? 'desktop' : 'mobile';
if (type !== this.state.type) {
this.setState({ type });
}
}; window.addEventListener('resize', checkMediaQuery);
checkMediaQuery();
} render() {
return this.props.children;
}
}MediaQueryProvider.childContextTypes = {
type: PropTypes.string,
};MediaQueryProvider.propTypes = {
children: PropTypes.node.isRequired,
};export default MediaQueryProvider;
And a HOC to wrap components and convert context to props
import PropTypes from 'prop-types';
import React from 'react';
import hoistNonReactStatics from 'hoist-non-react-statics';function getDisplayName(WrappedComponent) {
return WrappedComponent.displayName || WrappedComponent.name;
}const withMedia = (WrappedComponent) => {
class MediaQueryWrapper extends React.Component {
render() {
return (
<WrappedComponent
{...this.props}
{...this.state}
type={this.context.type}
/>
);
}
}MediaQueryWrapper.contextTypes = {
type: PropTypes.string,
};MediaQueryWrapper.displayName = `MediaQuery(${getDisplayName(WrappedComponent)})`;
return hoistNonReactStatics(MediaQueryWrapper, WrappedComponent);
};export default withMedia;
So then any child components can just have the type prop by wrapping with withMedia:
export default withMedia(TestComponentThatNeedsMediaType);See: https://github.com/DomainGroupOSS/react-media-query-hoc for a more detailed example of this pattern.
4. Don’t start using redux or mobx (or some other state management library) until needed
I have noticed that lots of developers like to start off a react projects with redux, mobx or some other state management library (especially new comers to React).
Before you do this — ask yourself the question:
“Can you accomplish the task easily with setState?”
Adding a state management library can be powerful but they add complexity you might not need. They are also likely to over complicate your application, making it harder for others to read and maintain. It also will pollute your application with an opinionated framework which may be harder to refactor later down the track.
I always like to start off with setState and if I notice that things are starting to get messy (eg: lots of shared state between components and lots of callbacks to trigger state changes for other components) then I will introduce MobX or Redux
Typical Application Examples:
- Your application is going to be relatively static in terms of interaction, maybe the only interaction is a simple enquiry form and the rest of the page is non interactive information — then
setStatewill do fine. - Your application is going to be a complicated beast from the get go, then you can consider using a state management library. This could be a CRM with lots of different actions and components on a given page.
Make sure you also check out Preethi Kasireddy’s awesome in detail video about the topic: https://www.youtube.com/watch?v=76FRrbY18Bs
5. Don’t have state all over your components — better to lift state up
If you lift state up to the parent component it means there is a single source of truth — which will make your application cleaner and easier to isolate bugs.
It also means if you want to use a state management library later, you can easily refactor your application to use it.
This has added benefit of making your components more reusable (as they don’t rely on application specific internal state)
Pro Tip: you can use a library likerecompose to help lift state and prevent abuse of setState all over your React lifecycle events eg:
const enhance = withState('counter', 'setCounter', 0)
const Counter = enhance(({ counter, setCounter }) =>
<div>
Count: {counter}
<button
onClick={() => setCounter(n => n + 1)}
>
Increment
</button>
<button
onClick={() => setCounter(n => n - 1)}
>
Decrement
</button>
</div>
)6. Don’t recreate functions and objects in render
If performance is a concern — avoid recreating functions or objects in your render. Not only will this effect memory and GC (usually only to a small extent) — this will also cause new references to be passed down to child components making shouldComponentUpdate checks harder and more expensive.
render() {
const onClick = () => {
.. do stuff
}; const data = {
stuff: 'blah blah',
count: 123,
}; return (
<div>
<MrComponent onClick={onClick} data={data} />
</div>
);
}
Try to pass props straight through in render — use class methods and if you have object constants declare them outside of your class or use static properties.
const data = {
stuff: 'blah blah',
count: 123,
};class ParentComponent extends React.Component { onClick = () => {
// do stuff
} render() {
return (
<div>
<MrComponent onClick={this.onClick} data={data} />
</div>
);
}
}
Note: when using Render Props (https://reactjs.org/docs/render-props.html) you’re effectively always passing down new references all the time which you need to be careful about. If performance isn't a concern and render props solves your problem elegantly then go for it !
7. Don’t make expensive shouldComponentUpdate checks
Avoid making your shouldComponentUpdate checks expensive — the checks should be quick and efficient, avoid things like deep equal checks on objects.
If you conform to step 6— then you can keep shouldComponentUpdate checks simple and cheap by just doing shallow equal checks. If any object or function reference changes then you can assume that a re-render is required. This also assumes that you should avoid mutating objects for the sake of performance.
import shallowequal from 'shallowequal';class MyComponent extends React.Component { shouldComponentUpdate(nextProps, nextState) {
return !shallowequal(this.props, nextProps) ||
!shallowequal(this.state, nextState);
} render() {
// render stuff
}
}
Or use React 15+PureComponent method:
class MyComponent extends React.PureComponent { render() {
// render stuff
}
}
Pro Tip: use https://github.com/garbles/why-did-you-update to help find unnecessary re-renders.
8. Don’t overuse HOC’s
I often see developers create HOC’s when it can easily just be a regular react component.
Michael Jackson (co-creator of React Router), tweeted this recently: https://twitter.com/mjackson/status/885910553432018945

To a large extent I agree, having HOC’s all over your code base doesn't exactly make it more readable and easier to debug.
There is also other implications such as the need to copy over static methods, not being able to pass through refs etc.
A good use case would be when you have multiple components that render different things, but have some identical implementation (eg. need to listen to screen / orientation changes).
A bad use case would be something like this (which can easily be accomplished by just passing in props normally which is more readable):
export default withProps(MrComponent, {stuff: 123});If you want to share code between React components, consider using the render props pattern instead: https://reactjs.org/docs/render-props.html which solves this in a more elegant / readable way — as you can see exactly where our state and props are coming from in the render prop argument list.
9. Don’t use state when you can use props or local instance variables
I always follow this breakdown from Dan Abramov:

Its perfectly fine to use a private fields if you are not using them in render and its also fine to calculate from props to avoid bloating state. This principle will also help in preventing unnecessary re-renders in your components.
10. Don’t use bind
Instead of binding functions that use this in your React components, use babel transform class properties https://babeljs.io/docs/plugins/transform-class-properties/ (stage 2 at time of writing) this will allow you to use the property initializer syntax:
class MyComponent extends React.Component { onClick = (data) => {
// do stuff
} render() {
return (
<AnotherComponent onClick={this.onClick} />
);
}
}
This will make your code cleaner and is a good pattern for beginners because they don’t need to worry about forgetting to bind functions.
See Ryan Florence’ (co-creator of React Router) tweet on the topic: https://twitter.com/ryanflorence/status/875485899525472256
