React best practises

A collection of useful tips/tricks and best practises regarding React in general and FDS specifically.


The best practise: RT(F)M

We recommend starting with the React documentation. Especially when you're new to React, their Main Concepts section provides an excellent general introduction and the Hooks section is a great follow-up. Then if you want to know more about certain Advanced Concepts, give those a read as well.

Best practises for functional components & hooks

Prefer them over class components when possible

In our experience, a functional component that uses hooks is much easier to understand than the equivalent class component.
This is especially true for components that have more than one concern or functionality to manage or implement.

Learn more about common built-in React hooks

There is a dedicated FDS playground section with experiments to help you understand how some of the most commonly used built-in React hooks work. And we've listed some best practises and tips to keep in mind when using them as well.

Best practices for class components & other (older) React concepts

Components, elements, Higher-order Components, JSX and ReactDOM.

React Component is a definition of a piece of UI (both the look and the feel). To use it somewhere you have to render it to the DOM(if using React for the web, which we assume to do for the rest of this guide). This is done using ReactDOM.render().

If you look closely at that documentation, you'll notice the first parameter to be 'element'. To create a React element, you generally use JSX.

JSX is compiled into JavaScript using a compiler like Babel. The compiled JavaScript uses React.createElement() to create React elements which describe a part of the resulting DOM that'll be created by React.

A React element can describe an instance of a React Component or a generic DOM node. In JSX, a Component is always <UppercasedLikeThis />. A DOM node is <always-lowercased-like-this />.

A special kind of Component (definition) is a so-called Higher-order Component, HoC for short. These are more advanced types of components explained well in the previously linked React documentation.
Their main purpose is to encapsulate and reuse behavior and logic across different components by only implementing it once. An alternative would be to implement that logic into a component which adheres to the 'children as a function' paradigm explained below.

Note on hooks: if you want to reuse behavior and encapsulate it using modern React tools, a (custom) React hook and using it in a functional component might be easier to understand and maintain.

Props, callbacks, render functions and children as a function

When defining a React Component, you can use props to accept data from parent components. A prop can also be in the form of a function/callback and can then be used to supply additional context from the component to the parent component.

So in simple terms, by accepting a callback, a component could make the parent component responsible for implementing certain parts of it. These parts can be anything from pieces of an algorithm (like a filter callback on an autocomplete to match the current text input to the appropriate item) or pieces of the actual UI (like a callback to render a specific card's content in a generic cards list component, these are usually called render functions/callbacks).

There is also a special prop in React called children, it is automatically filled with a special opaque data structure by React. You can also choose to expect (and call) a function instead of that special data structure when rendering children. This again allows you to pass some of the component's data to the parent component (where the children prop, now a function, is implemented / handled). This paradigm is called 'children as a function' and allows you to encapsulate behavior or logic into a single component, which can be instantiated and reused by other components. For example, a component like MousePosition could be used like this: <MousePosition>{(x, y) => ...}</MousePosition>.

This impacts the API of your component; the way it feels when instantiating it in JSX, they are otherwise functionally equivalent.

Note on hooks: if you want to reuse behavior and encapsulate it using modern React tools, a (custom) React hook and using it in a functional component might be easier to understand and maintain.

Context

As you might know, Context is a special set of APIs found on React Components to pass data along the whole component hierarchy without explicitly passing it via props. This can be very useful for certain features (routing, theming and localisation are the usual examples). In FDS and Fx, we also use context for similar system-wide concerns.

Over time, a lot of great recommendations around the use of Context have emerged. These basically entail: do not use it directly, use HoCs to wrap Context in your own APIs for easy upgrading as Context is eventually deprecated/altered by React.

We've followed this advice and therefore have our own components / HoCs that encapsulate our usages of (the now considered legacy) Context (for class based components).

When you’re customizing Fonto, you shouldn't have to use Context directly yourself. If you think you do, there might be a better solution available using plain React props and state.
And if you do find a good use case for context in your own components, consider using the modern hooks based implementation of context instead.

setState as a function of the previous state

When using setState to set a new state based on the previous one. Make sure to use setState with a function as the first argument (whose first parameter is the previousState). This ensures an atomic update and prevents race conditions.

Note on hooks: this also applies to the useState hook. Always prefer using it with a callback instead of providing it with a new state directly. This also makes your code more (React) future proof.

setState callback vs componentDidUpdate

When you want to ensure something happens after React has set a new state, you could use the second parameter of setState, which is a callback, fired when React has re-rendered the component. There is a lifecycle method on components that is also fired after each re-render, called componentDidUpdate().

We found this to be more reliable to use as it always fires after every re-render, not just after the re-render caused by a specific setState call, which is usually what you want to have happen anyway.

Note on hooks:  the useState hook also no longer supports the 2nd callback. Usually if you want to perform side effects (like you do in componentDidUpdate), the useEffect hook should be used.
If you want to derive state/data based on other pieces of data (from React props or state or ...), consider using the useMemo hook (or using memoization helpers in a class component).

componentWillMount vs componentDidMount

When we started with FDS, we used a lot of componentWillMount() to initialize timers, add callbacks to a Notifier on a manager, etc. After reading the documentation more carefully, we realized we should've used componentDidMount() for most of those instead.

This also forces you to create a state in your component where it can render before all (external) data is available. Making your component more robust in case the (external) data is slow to arrive for whatever reason.

This is especially relevant in Fx components which use managers to query for data they visualize as it becomes available. Most, if not all, managers expose their data asynchronously via some sort of change Notifier and a getter / property. Handling this correctly means adding a callback to the Notifier in componentDidMount and using setState inside the callback to trigger a re-render.

Note on hooks: in modern functional components, use (multiple) useEffect() hooks instead (one for each concern).

Lifting up state

One of the core principles of React is that to share data between several components, state (data) should be lifted up(in the component hierarchy) to the closest common ancestor.

When you build a component library like FDS. You'll realize that by following this principle, most components shouldn't manage their own state directly.

Instead they should accept a prop to inject data in the component and another prop (as a callback) to be triggered when the user interacts with the component in a certain way.

(Eg. a TextInput component has a prop called value to set the value, and an onChange callback prop which is called whenever the user (tries to) type a character in the rendered input DOM element. The component in which you render TextInput should implement the value as a state property and call setState whenever TextInput's onChange is called.)

This allows you to further lift this state as you need. If the state was already defined in TextInput, you couldn't lift it as this would require rewriting the TextInput.