The purpose of this document is to help guide you through working with a potential project using the JavaScript library, React. Outside of the official React documentation, you should find here personalized recommendations for using the library based on the types of projects we typically see at 10up. We have broken these recommendations out into common elements we tend to interact with often in client work. If something you’re looking for isn’t represented you can either submit a pull request to this repository or refer to the official React documentation.
Using the right tool for the job is critical in navigating a successful project. Will React always be the answer? Of course not. But there are some instances where you may want to use this library over something like (for example) a collection of plugins, custom JavaScript, or another framework.
When deciding whether React is the right tool for your project, it might help to ask the following questions:
React is easily integrated into specific parts of the front-end or admin of an existing site, but it can also be used to render entire sites, effectively replacing traditional WordPress templates—although doing so requires a lot more planning and scaffolding of features (such as routing) that would normally be handled by the CMS out of the box.
When building out components, it’s beneficial to understand how to construct them in the most appropriate way possible. Certain “types” of components can be written differently which can have big performance benefits on larger scale applications.
Class Components are written in the ES6 Class syntax. When building a component using a JS Class, you are generally inferring that the component manages it’s own state
or needs access to specific lifecycles hooks.
Example of a Class Component:
import React, { Component } from 'react';
class SearchInput extends Component {
constructor(props) {
super(props);
this.state = {
searchTerm: '',
};
this.handleClick = this.handleClick.bind(this);
}
handleClick(e) {
this.setState({ searchTerm: e.target.value });
}
render() {
const { searchTerm } = this.state;
return (
<div className="search-input">
<input onChange={this.handleClick} value={searchTerm} />
</div>
);
}
}
export default SearchInput;
Using class components are mostly discouraged since the introduction of React Hooks. There are however some situations where you might still need a class component:
componentDidCatch
for creating ErrorBoundaries.import React, { Component } from 'react';
class Camera extends Component {
handleCameraStream = (frames) => {
// this method has proper access to props and state. Even if Camera re-renders.
cancelAnimationFrame(this.rafID);
const loop = async (now) => {
const frame = frames.next().value;
// process frame
this.rafID = requestAnimationFrame(loop);
};
this.rafID = requestAnimationFrame(loop);
};
render() {
return <CameraStream width={width} height={height} onReady={this.handleCameraStream} />;
}
}
export default Camera;
onReady
is only called when the camera stream is ready and the handleCameraStream
method sets up a loop, if Camera
re-renders, onReady
is not called again. If Camera
was a functional component, everytime it re-rendered and its props/state changed, the handleCameraStream
callback would not have the right scope to have access to the most up-to-date state/props as the function was bound to the first render. The alternative for using functional components is storing each frame in state through useState
and process frames in a separate function but that adds complexity and unecessary re-renders every time a new frame is received.
A Functional Component can take the form of a plain function in JavaScript, or a fat arrow function stored in a variable. In the past, the biggest difference between a Class Component and a Functional Component was that Functional Components were not aware of state
. With the introduction of React Hooks functional components are now able to handle most of the React APIs such as state, contexts, refs and lifecycle.
Functional components are the recommended way to write React components as they come with less boilerplate code, allows you to reuse stateful logic without changing component hierarchy and complex components become easier to understand by avoiding a myriad of complex logic spread between componentDidUpdate
, componentDidMount
and other lifecycle class methods.
The following example is the previous SearchInput
component converted to a functional component using hooks.
import React, { useState } from 'react';
const SearchInput = () => {
const [searchTerm, setSearchTerm] = useState('');
const handleClick = (e) => {
setSearchTerm(e.target.value);
};
return (
<div className="search-input">
<input onChange={handleClick} value={searchTerm} />
</div>
);
};
export default SearchInput;
PureComponents allow for a potential performance benefit. A Pure Component implements the shouldComponentUpdate
lifecycle method to perform a shallow comparison of what changed in props
and state
since the last render.
Considering PureComponents perform shallow comparisons of previous state
and new state
, a component should become “pure” when theres no need to re-render the entire component (or its children) every time data changes.
Typically, you won’t need to create PureComponents as functional components and react hooks are better tools for the job. Do not use PureComponent if you are building stateless functional component and need lifecycle methods, use the useEffect
hook instead.
NOTE: The performance benefits are realized when the data passed to the component is simple. Large nested objects, and complex arrays passed to PureComponents may end up degrading the performance benefits. It’s important to be very deliberate about your use of this type of component.
In most cases, you will only need routing if your React application needs to navigate between multiple layout components, render different data based on the current app location, and provide browser history. Make sure your app needs routing functionality before you consider adding a routing library.
The most popular routing library for React is React Router. React Router provides a core library plus APIs for both DOM (web) and React Native (native iOS and Android) platforms. To use the library, install the package for one or the other API according to your application’s platform needs—the core library is included in both.
To read more about the concept of dynamic routing, with plenty of code examples to follow along with, refer to the React Router documentation.
In general, routing is little more than an Ajax call to load content with a URL update. This pattern poses some accessibility problems since there is no true page reload. To overcome this and make sure our React implementations pass accessibility compliance we need to ensure a few things happen:
Following these steps will make sure your content and routing is readable by assistive technology.
State in React is the lifeblood of the component. State determines how, and with what data, a component will be rendered on the page. State gives components reactivity and dynamic rendering abilities.
Props serve as a means to pass data between components. At its most basic level, props are passed to each individual component that needs to consume and utilize that data. Basic applications will likely be able to pass data using this default behavior.
As an application becomes more complex, it may become more of a hassle to pass data down to many child components. This is where frameworks like Redux will come in. However before you reach for these third party frameworks, consider the React Context API
The React Context API is the first line of defense when your application becomes sufficiently complex, and we are faced with prop drilling concerns.
Context provides a way to pass data through the component tree without having to pass props down manually at every level. This is immensely helpful for applications that are highly componentized, and need to share data with those components, regardless of where they exist within the application structure. It is crucial that you think critically about how the data in your application is to be utilized and passed around. If data simply needs to be shared, Context may be for you.
Context does not however provide the further sophisticated features of libraries like Redux. Stepping through application history, alternate UIs that reuse business logic, state changes based on actions etc. If those are things that you need in your application, the Context API may not be quite robust enough for you.
You also need to be careful with the fact that any component “connected” to a given React Context will re-render automatically when the data the context holds changes. There’s no built-in mechanism to mapStateToProps within the Context API. One way to solve this problem is to create multiple specialized contexts for you application that only stores a specific portion of you shared global state. For example, a “User” context that holds user data and a “Posts” context that holds a list of posts to be rendered on the application.
For example, consider the following UserProvider
component that is responsible for fetching an user if it’s logged in and storing the user object in a React Context.
import React, { useEffect, useState, useContext, createContext } from 'react';
import PropTypes from 'prop-types';
import { fetchUser } from '../util';
export const UserContext = createContext();
export const useUser = () => {
const data = useContext(UserContext);
return data.user ? data.user : null;
};
const UserProvider = ({ children }) => {
const [user, setUser] = useState(null);
useEffect(() => {
fetchUser().then(setUser);
}, []);
return <UserContext.Provider value=>{children}</UserContext.Provider>;
};
UserProvider.propTypes = {
children: PropTypes.oneOfType([PropTypes.element, PropTypes.arrayOf(PropTypes.element)])
.isRequired,
};
export default UserProvider;
This component can then wrap the application tree exposing the UserContext
to any child component.
import React from 'react';
import UserProvider, { useUser } from './UserProvider';
const UserMenu = () => {
const user = useUser();
return user ? 'user is logged in' : 'user is not logged in';
};
const App = () => (
<UserProvider>
<UserMenu />
</UserProvider>
);
export default App;
As an application grows larger, it may be the case where the state becomes difficult to handle, every new feature introduces a new layer of complexity that may in some cases result in unexpected, and unpredictable behavior.
Redux is a state container that stops the ever-changing nature of the state itself. It acts as a protector of the state, allowing only certain defined actions to trigger a state update. Thus making it predictable.
Principles of Redux
Redux is based almost completely on 3 main principles:
The third bullet point is really important but some times underestimated. Introducing application side-effects inside reducers can trigger several issues that become difficult to debug. As a general rule, action creators and reducers should never trigger application side-effects such as mutating the DOM, attaching event callbacks, triggering event callbacks etc. If you need something like that consider either creating a Redux Middleware or using existing solutions such as Redux Saga alongside with Redux.
When to use Redux
As appealing as it might be. Redux is not a tool for everyday use. By design, Redux will put constraints in your application that may not actually be needed. A good starting point to make this choice is the article by its creator, Dan Abramov: You might not need Redux. The usual recommendation is, think in React. And if along the way you discover the need of Redux, implement it.
The idea of resilient components was first introduced by Dan Abramov, his blog post writing resiliting components does a great job explaining this concept.
The principes are the following:
Writing resilient components makes components more robust and has the potential to avoid many bugs. It’s highly recommended to review the principles above.
React accessibility is not so different than standard accessibility support. It mainly centers around making sure semantic HTML and proper attributes are used with the correct elements. Managing focus flow and repairing when necessary. Be sure to also use the jsx-a11y eslint plugin to ensure your code maintains a solid accessible foundation.
Using the the most relevant HTML elements is always preferred, and doesn’t change when using React. Sometimes we can break HTML semantics when we add div elements to our JSX to make our React code work, especially with a set of elements such as lists. In these cases we should use Fragments.
Fragments are a pattern used in React which allow a component to return multiple elements, without an encompassing div component. Using div elements in certain contexts may break HTML semantics.
Use fragment when a key prop is required
Use <elem></elem>
syntax everywhere else
Standard HTML practices should be used for forms. One caveat though is making sure all inputs have proper labeling. In React the “for”
attribute is written as “htmlFor”
in JSX.
<label htmlFor="namedInput">Name:</label>
<input id="namedInput" type="text" name="name"/>
React applications continuously modify the HTML DOM during runtime, sometimes leading to keyboard focus being lost or set to an unexpected element. React provides “Refs” in order to modify child components or elements outside of the standard flow. These can help us specifically manage keyboard focus.
The ref attribute can be placed on any React component, with a function value. This function will be executed as soon as the component is mounted or unmounted. The first parameter of the function will be a reference to the element or the component the ref is on.
Read more about creating refs and focus control in React
With server side rendering (SSR), the initial content is generated on the server, so your browser can download a page with HTML content already in place. Updates to the content are still handled in the browser.
Here are three topics to consider when looking at server-side rendering:
If you’re only investigating SSR to improve the SEO of a handful of marketing pages, you probably want prerendering instead. Rather than using a web server to compile HTML on-the-fly, prerendering simply generates static HTML files for specific routes at build time. The advantage is setting up prerendering is much simpler and allows you to keep your frontend as a fully static site.
Another alternative for improving SEO on react websites without SSR is to use Dynamic Rendering. This requires a more complex set up but the benefit is that this technique does not require any changes to the SPA codebase.
The idea here is to set up a server or service that will be responsible for prerendering the SPA through a headless browser before serving the markup to search engines.
React provides a Chrome & Firefox extension to facilitate debugging. It is an extremely useful debugging tool, providing quick transparent access into the data within your React instance. Whenever you encounter a new concept in React, it’s generally a good idea to open up the dev tool, and observe your application state.
Before we dive into some of the specifics of Gutenberg, it’s important to understand React’s role within Gutenberg itself.
Very simply, Gutenberg is built with React. The underlying code that makes Gutenberg work, is React code. Much like “WordPress PHP” Gutenberg blocks employ a similar code style as React, but it’s not React. In any case, the most effective way for us to frame React’s role in Gutenberg is that React is simply the technology used to make Gutenberg work the way it does.
When discussing Gutenberg and its capabilities, we must examine its role in any given project. Primarily, Gutenberg should be framed as a tool for publishers to manage their content in a more dynamic and flexible way. It should not be framed as a “page builder”, which implies a 1:1 relationship between the front end and the back end. Gutenberg most certainly has tools to help us achieve a 1:1 relationship, however creating this relationship for every custom block should not be an implied expectation.
When creating Gutenberg components in the WordPress editor, mostly you’ll find yourself adhering to the standard best practices of React, but there are a few Gutenberg-specific design patterns you should be aware of before starting a new build.
Element is an abstraction layer atop React created just for WordPress and used within Gutenberg components. It was created to allow engineers an API entry point into Gutenberg with deliberate features, omissions, and protections from core-library updates (React updates, in this case) that could cause breaking changes in an interface.
The presence of Element is why you don’t see React directly imported into Gutenberg components. Read more about using Element in Gutenberg.
Gutenberg offers a library of higher-order components (HOC) you can use to build out a robust editor experience. The features of these components range from focus management to auditory messaging. It is best to familiarize yourself with these components so you don’t end up rebuilding a utility functionality that already exists within Gutenberg. You can view Gutenberg’s library of generic Higher Order React Components to learn more or view the official React documentation for general information about using HOC.
As with any evolving feature, it is important to frequently check the documentation for new additions and updates.
Gutenberg has the ability to allow only certain blocks to be used. This can be found under the Block Filters page of the official Gutenberg documentation. Specifically, under the heading Removing Blocks. Details of how to filter via JavaScript or PHP can be found there.
Also note that this method is different than the allowedBlocks
prop on <InnerBlocks>
when used in a custom block. The filtering described here applies to all blocks on a higher level within the editor.
There may be a need to remove or restrict access to blocks. This could be for a variety of reasons, but typical scenarios could be:
Each project is different. Carefully consider removing blocks from the admin with your project team.
Typically, you should exclude all blocks by default and add a list of which blocks to include versus including all blocks by default with a list of which to exclude. This helps guard against changes or blocks which are added at a later date, which could break functionality or have other unintented side effects. Blocks would have to be specifically allowed and tested before being used. Additionally, typical patterns for how we develop favor this in other locations (such as escaping, <InnerBlocks>
, etc.) This helps keep a consistent pattern in the codebase.