I’ve spent a good deal of the past year working with React. I’ve written, refactored, and re-written many components in that time, and I’ve seen some best practices and anti-patterns emerge. This resource contains a collection of React best practices and React tips provided by BrowserStudios. This is a community driven project, so you are encouraged to contribute as well, and we are counting on your feedback. Its about:
- Avoiding styles and
classNames
- Component’s properties are API’s
- Use Flux and build a uni-directional data flow
- Controlled or uncontrolled inputs
React is a library that helps to create web UIs that has revolutionizes the way we think about apps. Borrowing ideas from declarative, reactive and functional programming, it makes developing highly interactive UIs a breeze. As such, it is something every frontend developer should have in their tool belt.
Controlled or uncontrolled inputs? Let’s talk about controlled inputs that update states and cause and re-rendering the component on change events. Could that be best practice? The good thing is that component have full control of what happens, instead of having defaultValue parameters, and on some event, the component extract the values, update the entity and save it to the server. Is that everything?
Properties and States
Updating individual properties. For example, you can use switch based cases on what input field is being update. What I much rather want to do is I could instead make sure all field names are in sync with the property names of the entity, and I can set properties of the entity object in handleChange based on the name of the field from the event. However, this makes it hard to have individual needs per field, even if most fields just update an entity property directly. I guess an alternativ is a switch with a default block setting based on the name of the input, and case blocks for any field that require other ways of updating.
Updating the state entity. The React pages say you should never update state directly. One of the reasons is something I have experienced when updating the state this way before calling setState. If you have a shouldComponentUpdate method, the prevState contain the new state, since the content of the prevState argument sent to the shouldComponentUpdate is based on what was in the state when setState was called.
I choose to split the life-cycle methods into those that occur before an instance of the component is created (e.g.
getInitialState
,getDefaultProps
) and those which occur duringcomponentWillMount
andshouldComponentUpdate
. Furthermore, I find that declaring the lifecycle methods in order of execution also makes the component easier to reason about.
Knowing where to draw the lines between components
React is all about Elements. Elements take a set of attributes, have children, and emit events. Something they have in common, they always manage their own state. Consider an
element. Id, or style attributes are optional. It has a required src
attribute, and width
and height
properties. It does have some state that it manages: initially, and the progressive display of the image as it loads. When the image is fully loaded, it emits a specific load
event.
Having a lot of components isn’t bad. You can have a lot of tiny components. When deciding the boundaries think about these (in terms of requirements changing tomorrow). For siblings (multiple components with similar api, not nested), will I need to update these components all at once? As an example: a primary button is something you want to change once and have it affect everything, will I need to have differences between these components? Here is a good example of how detailed a component button can be.
class Button extends React.Component { static propTypes = { kind: React.PropTypes.oneOf(['primary', 'warning']).isRequired }; render() { return ( {this.props.children} ); } }
For parent/child, does splitting make testing easier? A list of inputs, testing events for one input is simpler than testing a list of inputs. Will it reduce the complexity by splitting? Than separate it but merge when splitting result it pointlessly tiny components. Having unnecessary markup happens in react.
Use React.PropTypes
As your application grows and you start composing components into complicated hierarchies, hunting down missing or improper props
can be a pain. React, and so does TypeScript, offers a way to specify which types a component expects for each of its props
and which props
are required:PropTypes
. A log warning in your console will tell you what went wrong. Furthermore, it provides a reference for how the component should be used, and what props it needs to be passed. Its just such a nice and convenient way to document what props
your component expects.
I like to declare
propTypes
at the top of my component as they are a useful guide to a components expected usage, followed by themixins
because it is handy to initially know exactly what external behaviours the component is using/dependent on.
Use stores and centralize your states
Pull the state completely out of the component hierarchy and into a central state object. Have the state flow down through your components purely as props
— have no state actually namaged by components. This is a central theme of the Flux architecture. You have a set of Stores holding your application’s state and an event-driven action map that alters your Stores and your entire application state is stored. Uni-directional data-flow is the goal.
This means that your components must primarily rely on props
. Instead of calling setState
, they communicate with a central data store, not events, channels, callbacks, etc. directly. That means a central data store contains a representation of your app’s state, and triggers a re-render on the entire application on every change. React is built around efficient updates to the DOM. This not really obvious, but having tried it other ways, I’ve come to realize it is a good way to create anything larger than a trivial application. A need side effect, if you build after the flux architecture and use the way it is supposed to be used you end up with a very flexible architecture where data can come from MongoDB, SQL, API, etc. Your app becomes simpler and more manageable.
You should consider to do more in the render function
If you find yourself putting lots of logic into componentWillReceiveProps
orcomponentWillMount()
, instead, see if you can move that logic to render()
. It’s okay to repetitively recompute some things in certain cases. PureRenderMixin and Immutability
will help to minimize render()
calls, so trying to work around it is a stereotypical premature optimization. It turns out this will actually lead to fewer and more-obvious bugs, and will also prevent you from duplicating code in componentWillMount
and componentWillReceiveProps
.
let {BindComponent, ListWrapper} = Classes; class UserWrapper extends BindComponent { /** * Constructor for class * @param props */ constructor(props) { super(props); this._bind( '_render' ); } _render() { let {dataObject, filterText} = this.props; if( !_.isEmpty(dataObject) ) { return (); } else { return (We have no friends.); } } /** * Compute more inside the render function.. * @returns {XML} */ render() { let element = this._render(); return ( { element } ); } } UserWrapper.displayName = 'UserWrapper'; UserWrapper.propTypes = { filterText: React.PropTypes.string } _.extend(Classes,{UserWrapper});
I like the render
method to always be last. The render
method is a mandatory lifecycle method and it’s almost always the function I need to find first when I open a file. Consequently, it’s pragmatic to have it in a consistent location across all of my components.
I always have my custom methods follow the lifecycle methods and be prefixed with an underscore to make them easier to identify. I’ll usually also group these by utility (parsers, event handlers, etc).
In-line list iteration
Where possible, I like to iterate over lists of data in-line in the returned JSX unless its internal logic is sufficiently complex enough to warrant moving outside of the return statement and populating an array for rendering.
render(){ return ( {this.props.list.map(function(data, i) { return () })} ); }
Indentation and new line for component attributes
When there are enough attributes on a component that displaying them inline becomes untidy (usually 3 or more), I always display them on multiple lines and indent each one. i.e.
Conclusion
These guidelines are by no means authoritative or exhaustive, but I feel they are a good starting point for organising and standardising React components and some of the more common use cases I encounter.
But here are some of the things I mentioned. Avoiding styles and classNames
forces you to separate styling concerns like how it looks vs how it lays out, which means you’ll have different components to enforce each. The parent component will take care of Input
and Buttons because they should not be concerned about it
.
Think about components’ properties as their API. Exposing styles or classNames
is like exposing an ‘options’ object which can have hundreds of options. Do you really want that for your own components? Do you really want to think about each possible option when you use them?
It’s better to leave styles and className
to be used in your leaf-most components, and generate a set of components that will build your look and feel, and another set that will define your layout.
When you do this, it will be much more simple to create new components, views, and pages of your application, since you’ll just need to arrange your set of appearance and layout components and pass them the correct props without even thinking about CSS or styles.
And as an added benefit, if you want to tweak the styles or change the look and feel of your application, you will mostly just need to modify the library components. Also if you keep the same API for them, the components that use them will remain untouched.
So, if you’re ready to partner with a web developer who can truly harness the potential of React then let’s connect.
Email – michel.herszak@gmail.com
Twitter – @MHerszak (twitter.com/MHerszak)
Want to know about the way in which I work? Great, you can get started with an overview of just how I approach a project right here.