React: Uniting Companies
Since 2019!

  • Principal Engineer for ShipStation
  • 14 years of development experience, front-end focused for 9 years. 5 years of React-specific experience dating back to v0.8.
  • https://www.linkedin.com/in/richardlindsey/
  • Die-hard Star Trek fanatic, cruising enthusiast, and code monkey.
  • Web Development Manager at Stamps.com
  • 21 years of web development experience stretching across multiple industries and small to fortune 500 companies
  • https://www.linkedin.com/in/uiuxdeveloper/
  • Hobbies include scuba diving, camping and generally being active outdoors.

Richard Lindsey

Curtis Janczak

Table of Contents

History of Web Apps

Full stack engineers spend tons of time authoring code that runs the entire gamut from database queries and stored procedures up through HTML and Javascript to be executed on the client.

Code abstractions, at least on the client, were minimal, if they were even used at all. Engineers had to write tons of one-off functions and gated code to handle differences between Netscape, Internet Explorer, Safari, and Firefox's Javascript implementations.
These were truly the Dark Ages of the internet.

History of Web Apps

jQuery explodes onto the scene and garners huge market share by providing a normalized API for the most common web-based tasks, internalizing browser differences so engineers don't need to worry about them.

Its API is loved for its simplicity and its declarative nature, as well as its overall developer experience, offering a set of chainable functions, collections as first-class citizens, built-in AJAX functionality, motion-tweening effects, etc.

History of Web Apps

Tends of thousands of lines of jQuery spaghetti code begin clogging up the tubes of the internets. jQuery UI is constructed, based on jQuery's own plugin system, to allow for true component-based development. This starts to fragment the concept of full-stack engineers into those that specialize in either front- or back-end engineering.

It's also still heavy-handed in the way it handles its DOM manipulation. Entire trees of DOM nodes are removed and replaced as state changes occur within components, and state management is an entirely-manual operation.

History of Web Apps

Along comes Backbone to help add some long-needed structural support for thick-client applications. It offers proper data model support, with a pub/sub mechanism to automatically re-render views that are bound to those models. This is a step in the right direction for automated state management for view-models.

RequireJS becomes the standard mechanism for the first wave of Javascript module support, allowing components to be isolated into separate files and easily aggregated into Javascript bundles via utilities like Browserify and Gulp. The concept of devops specifically geared towards the front-end starts to rear its ugly head.

The Framework Wars

Libraries like Backbone soon finds themselves in contention with frameworks like Marionette for scaling issues, as well as Angular, Ember, Knockout, and others based on wholly different target-markets of engineers.

A lot of these frameworks have their own good and bad ideas to contribute to the overall goal of client-side applications, whether it's dependency injection, observable data models, computed fields, or anything else. Most of them have a lot of boilerplate syntax in order to bootstrap an application, or foreign syntactic constructs that just aren't powerful enough for robust components.

KnockoutJS hits the scene. One of its biggest strengths are that it offers observable objects that can be used as data models to drive UI changes. Passing your object to ko.observe returns a new subscribable version of it, which can be used within views and callbacks. Updates are automatically flushed to the views.

One of its greatest strengths also becomes one of its greatest weaknesses. Applications are littered with ko.observe calls all over the place. The template syntax also requires a custom data-bind attribute that shims some portion of Javascript into the template.

The Framework Wars

EmberJS is another competitor in the framework wars. It's highly performant, provides custom tooling in the form of a custom inspector and a CLI tool to quickly scaffold out new components. It offers computed fields for call-time values, and a pretty robust API layer if you're using RESTful interfaces.

Unfortunately, it had a pretty large footprint (upwards of 120k gzipped), was difficult for a lot of people to learn, and the team behind it had a slower release cadence with smaller feature sets than the community desired. It was also pretty tightly coupled to jQuery itself for its AJAX and DOM-manipulation API layers.

The Framework Wars

Angular is released by Google, and emerges as a clear frontrunner. It has terrific ideas around dependency injection and module testability, especially when pairing with a test library like Protractor. It's opinionated with regards to application authoring, offers view-model bindings to automatically update views, and directives allow for component-based development that reads like true web components in its templates.

It also had a pretty steep learning curve, and some of its abstractions were a little unclear, such as the differences between services and providers. Two-way data binding had some severe performance issues, and the template syntax was horrendous.

The Framework Wars

<button (click)="onSave($event)">Save</button>

<button *ngFor="let hero of heroes" (click)="deleteHero(hero)">{{hero.name}}</button>

<form #heroForm (ngSubmit)="onSubmit(heroForm)"> ... </form>  

Around the same time, Facebook released React. It was unopinionated in nature, allowed for component-based development, was focused on composition over inheritance, and made for simple testing through the Jest and Enzyme test libraries. It also offered intelligent UI reconciliation against state-based development, taking the aspect of DOM management away from the user, and allowed access to 100% of the Javascript library as part of template authoring.. The react-devtools utility was released as a browser plugin, making debugging much easier.

Its unopinionated nature was also a turnoff for some, who found that it needed to frequently be paired with other 3rd party utility libraries. JSX was also a tough pill to swallow for some people.

The Framework Wars

History of Web Apps

Not only does React + JSX make component authoring a snap, but the React Native project helps front-end engineers get even more reuse out of their components, by translating them to native-style components on the Android and IOS native platforms. Now front-end applications can start to approach the ideals of Java's "Write Once, Run Many" mantra without the need for bloated VM's.

JSX becomes an abstraction used across multiple libraries, to allow for HTML-like syntax that can be transpiled into any number and shape of function calls via custom pragma functions, allowing it to be used by React, React Native, Angular, Vue, and others.

History of Web Apps

State-driven UI becomes a common practice, and React's functional lifecycle hooks help automatic the timing sequence of flushing state changes out to the DOM. The engineer no longer needs to worry about syncing their state to their UI, as the library handles it. This allows more asynchronous code to flourish, and for applications to take full advantage of Javascript's event-loop-based concurrency model. It really doesn't matter when a task completes; so long as the state is updated faithfully, the UI will also be updated faithfully.

React Highlights

The UI can be thought of as a function of state.
At any given time, in an ideal world:

UI = fn(state);

The React library allows an engineer to focus solely on the tasks and business logic that drive the state changes within an application, and to not have to worry about flushing or syncing those state changes to the DOM themselves.

React Highlights

In addition to the benefits of its lifecycle methods managing that timing for you, it has the added benefit that it is literally "just Javascript". Code written for a React component is either a JSX abstraction, or pure javascript. The JSX abstraction breaks down as simple as follows:

<div>
  <p foo={true}>
    <span bar={() => true}>Hello World!</span>
  </p>
</div>
React.createElement('div', null,
  React.createElement(
    'p', { foo: true },
    React.createElement(
      'span, { bar: () => true },
      'Hello World!'
    )
  )
);

React Highlights

React comes with its own built-in timing mechanism for propagating state changes to the DOM. This process is known as reconciliation. The component lifecycle methods give you the ability to run tasks at predetermined points during that reconciliation cycle, without having to worry about when exactly those changes get flushed to the DOM.

With these lifecycle hooks, it becomes possible to use these components as generic declarative wrappers for basically anything that has a predetermined setup and teardown cycle. The obvious use case is to write components for visual pieces of UI that mount and unmount based on state, but you can just as easily write components that declaratively wrap native or 3rd party APIs.

import React from 'react';

const GoogleAnalytics = class extends React.Component {
  componentDidMount() {
    const { event, id } = this.props;
    
    (function(i,s,o,g,r,a,m) {
      i['GoogleAnalyticsObject'] = r;
      i[r] = i[r] || function() {
        (i[r].q = i[r].q || []).push(arguments)
      }, i[r].l = 1 * new Date();
      
      a = s.createElement(o), m = s.getElementsByTagName(o)[0];
      a.async = 1;
      a.src = g;
      m.parentNode.insertBefore(a, m)
    })(window, document, 'script', '//www.google-analytics.com/analytics.js', 'ga');

    ga('create', id, 'auto');
    ga('send', event);
  }
  
  render() {
    return React.Children.only(this.props.children);
  }
}
import React from 'react';
import ReactDOM from 'react-dom';

import App from './app';
import GoogleAnalytics from './google-analytics';
import { gaId } from './google-analytics/config.js';

ReactDOM.render(
  <GoogleAnalytics id={gaId} event="pageview">
    <App />
  </GoogleAnalytics>,
  document.getElementById('root')
);

React Highlights

React offers 3 built-in methods for dealing with stateful data within your components: State, Props, and Context. Generally speaking, you should try to confine the bits and pieces that comprise your application state within the lowest-level container that needs access to it.

            (C)
           /   \
          /     \
         /       \
        /         \
       /           \
     (A)           (B)
    /   \         /   \
   /     \       /     \
(A,C)    (A)   (B)    (B,C)

React State

React state is local to a given component. It may be managed within that component itself (think of a Dropdown with a click handler to toggle 'isOpen' between true and false), or possibly by some child component (think of a Modal that has a subcomponent for a CloseButton which, when clicked, toggles the Modal's 'isOpen' state from true to false.

You update a component's local state via the setState function (in class-based components) or via the useState hook's provided setter function. Updating a component's state will trigger a reconciliation cycle, allowing the UI to be synced against the new state value(s).

React Props

Props are input values received from a component's parent. They may be sourced originally from some ancestor component's local state, some upstream context provider, or some computed value, but they always originate from a component's parent. Props are read-only, and when they update anywhere upstream, will cause a reconciliation cycle, with the target component automatically receiving the updated prop values.

A separate PropTypes library is provided in order for component authors to dictate the format(s) allowed for incoming props. This helps establish a proper API interface for the target component.

import PropTypes from 'prop-types';
import React from 'react';

import Dropdown from './dropdown';

const ControlledDropdown = class extends React.Component {
  static propTypes = {
    isOpen: PropTypes.bool.isRequired,
    options: PropTypes.arrayOf(PropTypes.shape({
      display: PropTypes.node.isRequired,
      value: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired,
    })).isRequired,
    value: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
  };
  
  render() {
    const { isOpen, options, value } = this.props;
    
    return (
      <Dropdown isOpen={isOpen} options={options} value={value} />
    );
  }
};

React Context

React's context model uses a provider pattern to deploy values from some ancestor component to some child component that may be deeply nested in the component tree, without having to pass those values as props through every intervening layer of components. It can be a handy tool for providing global behaviors, configuration settings, stylistic themes, or anything else your application might have a need for in multiple places.

React context components are created with the React.createContext function. It accepts an optional parameter for an initial value. If not provided, the initial value is published as undefined. The Consumer component takes a functional child, which will be invoked with the current context value during the render cycle.

React Context

React context components are created in pairs, always one Provider and one Consumer component. Most people tend to wrap at least one of those in some component layer for exporting, or else it's not very useful. Consumers will always seek out their closest ancestor Provider component to pull their values.

Provider components themselves may be nested within the component tree at various layers, allowing one to change the value of the provided context at that layer of the application. Any Consumer components nested below that point would receive that Provider's context value instead of the one higher up the tree.

JSX

JSX is an abstraction written on top of Javascript, to allow for an HTML-like syntactic structure to declaratively call various functions. These functional pragmas are can be defined by the library in question, which is how libraries like ReactDOM and ReactNative can interpret the same React views and product different output, specific to their own platform. ReactDOM produces DOM nodes, and ReactNative produces either Objective-C or Java output, for use in a particular native platform.

Alternatively, a library like Vue or Angular could also use JSX with their own custom pragma to direct a transpiler to output that syntax as their own set of function calls. 

JSX

Because JSX is just a sugary syntactic coating on top of generic function calls, and prop attributes are just a different way of representing input parameters to those function calls, it means you can pass any data type as a prop, in the same way that you can pass any data type as a function's input parameter. Complex objects, callback functions, promises, resource identifiers, or anything else you like can be passed as a prop value with JSX. It also means that, outside of writing functions as a JSX abstraction within your views, you can also embed any literal Javascript you want as part of your views. React likes to tout itself as "just Javascript" because it is. Anything you can do in pure Javascript, you can do in a JSX React template for any view you're authoring.

import React from 'react';

import Greeting from './greeting';
import HelloWorld from './hello-world';

const MyView = ({ name }) => (
  <div>
    <Greeting name={name} />
    <HelloWorld />
  </div>
);

export default MyView;
import React from 'react';

import Greeting from './greeting';
import HelloWorld from './hello-world';

const MyView = ({ name }) => (
  React.createElement('div', null,
    [
      React.createElement(Greeting, { name }, null),
      React.createElement(HelloWorld, {}, null),
    ],
  )
);

export default MyView;
import Greeting from './greeting';
import HelloWorld from './hello-world';

const MyView = {
  props: ['name'],
  render(createElement, { props }) {
    return (
      createElement('div', {},
        [
          createElement('greeting', { name: props.name }, null),
          createElement('hello-world', {}, null),
        ]
      )
    );
  },
);

Vue.component('my-view', MyView);

export default MyView;
import Greeting from './greeting';
import HelloWorld from './hello-world';

const MyView = {
  props: ['name'],
  render(createElement, { props }) {
    return (
      <div>
        <Greeting name={props.name} />
        <HelloWorld />
      </div>
    );
  },
);

Vue.component('my-view', MyView);

export default MyView;

Break! 10 Minutes!

React API

React.Component and React.PureComponent are both used for creating class-based components with access to all of the various lifecycle functions. 'Pure' components automatically implement a shouldComponentUpdate function that does a shallow comparison of any prop or state values. If the values of all props and any existing state variables are strictly equal from one render cycle to the next, React bails without recalculating any virtual DOM at or below the current level of the application.

  • React.Component
  • React.PureComponent
  • React.memo

If you're not using any local state or lifecycle functions, you can get away with writing a regular functional component. If you need a 'pure' functional component, you can use the React.memo function, which provides similar functionality, checking incoming props for strict equality from one render cycle to the next.

React API

React.createContext is used to create a pair of components for sharing and updating context values within hierarchies of your application's component tree. You wrap the root of a particular application hierarchy (either the root of the application itself, or some layer of the application to which that context is applicable) in the Provider component, and any component that needs a value from that context simply wraps itself in the Consumer, extracting any values it needs from that context.

  • React.createContext

React API

The React.createElement and React.createFactory functions are typically used by the transpiler to convert declarative JSX into the imperative function calls that will render your application. They are exposed as part of the top-level API, but most developers will never actually use them, as they'll be authoring components via the JSX abstraction. React.createElement receives a component (string, function, or class), props, and children, and outputs the virtual DOM result, and React.createFactory receives a component (string, function, or class) and outputs a function that just receives props and outputs the virtual DOM result.

  • React.createElement
  • React.createFactory

React API

React refs allow you to receive a handle to either a component instance, or an underlying HTMLElement in the DOM, representing a component instance. The 'ref' prop is used to assign a reference handle (created with React.createRef) to a component. If assigning to a host component (e.g. div, span, p, etc.), the 'ref.current' property will contain a reference to the HTMLElement that represents it. If assigning to a complex component (e.g. App, DisplayGrid, etc.), the 'ref.current' property will contain a reference to the instance of that React element.

  • React.createRef
  • React.forwardRef

The 'ref' prop is not accessible from inside a component it's been applied to, at least via the props object. In situations where you want to expose some underlying reference, such as the internal 'form' HTMLElement on a 'Form' component, rather than the instance of the 'Form' component itself, you can use the React.forwardRef function on the target component, which will be passed the incoming ref object as a second parameter. You can then apply it to whatever underlying node you want.

Note that React.forwardRef only works on functional components, so if you're trying to forward an incoming ref to a class-based component, that component will need to receive it as a different prop name (since 'props.ref' is not exposed for use), and then will need to be wrapped in a functional component that can proxy the incoming 'ref' through to that custom prop name. This allows external consumers to always pass a consistent 'ref' prop, without needing to be aware of any custom prop name to use.

React API

Currently, React only allows a single root node to be exported from any place where you're constructing JSX, whether it's for return from the render function, or as some conditional snippet of JSX inlined in your view, or anything else. In the past, when people wanted to output sibling components, they would have to wrap those components in a 'div', or 'p', or something else in order to create a single root node around those siblings. This may not always have been desirable, however, based on CSS rules in place or the intended semantic structure of the markup.

  • React.Fragment

React.Fragment was created for these situations, to allow a component author to export a single root node without actually specifying a host component wrapper for it. In the output markup, the nodes will render as intended, as siblings with no specific parent element to contain them, but internally, React can still treat them as a single 'component'.

import React from 'react';

const ComponentList = ({ items }) => (
  <React.Fragment>
    {items.map(item => (
      <li key={item.guid}>{item.display}</li>
    ))}
  </React.Fragment>
);

const MyLists = ({ items }) => (
  <React.Fragment>
    <ol>
      <ComponentList items={items} />
    </ol>
    <ul>
      <ComponentList items={items} />
    </ul>
  </React.Fragment>
);

React API

React.Children is a special namespace that contains functions for dealing with anything that could be rendered as a component's children. In React, the 'children' prop is special, in that it may be a single React element instance, a string, a null value, or an array of any combination of those things. The React.Children functions provide an abstraction that basically always treats the passed 'children' as an array for the purposes of iteration, or allows you to specify that they must be a single root node.

  • React.Children
  • React.cloneElement
  • React.isValidElement

React.cloneElement can be used against a single React element instance, and allows you to create a copy of that instance for the purposes of manipulating it. This could be to add an extra 'className' prop, or a custom 'onClick' handler, or a generated 'ref', or anything else that should be applied as a transform. In conjunction with React.Children iteration, you can conditionally apply things based on component type.

React.isValidElement allows you to check whether a given object instance is actually a valid instance of a React element. If you're iterating over some children for the purposes of cloning them, you may want to conditionally apply the clone based on whether a particular child is an actual React element instance, vs being a string or a null value.

React API

React.lazy is part of an emerging API and development paradigm being worked on by the React team. The ES2020 spec is due to implement a dynamic form of the 'import' keyword. Webpack supports this by way of creating a new chunk and writing an internal snippet to load it when that code path is traversed, but soon enough, ES modules will be able to do this natively. React.lazy builds on top of this by creating a component wrapper that will render when the import promise is resolved.

  • React.lazy
  • React.Suspense
import React from 'react';

let moduleDefinition;

import('./some-module').then(mod => {
  moduleDefinition = mod;
});

const DynamicallyLoadedComponent = React.lazy(() => import('./my-component'));

These component wrappers can then be added to your views anywhere you wish, so long as they reside below a React.Suspense component. React.Suspense supports a (required) 'fallback' prop to dictate what should be displayed when a lazy component is still in its loading phase. When the load is complete, the actual component will be rendered in place of the fallback.

Multiple React.Suspense components can be embedded throughout your application. Any React.lazy components embedded in your views will display the fallback component of their closest ancestor Suspense component. When React's new concurrent rendering mode is officially released, it will be able to determine when to display a fallback component as opposed to rendering nothing, if the loading promise is resolved quickly enough.

React API

The hooks API is one of the newest additions to the React project. It provides a set of base functions for you to either use as-is, or to compose into higher-order hook functions for your own use. Hooks are only available for use in functional components, not class-based ones, however, with hooks, you should be able to author functional components 100% of the time. Hooks will allow you to add state, provide context, create memoized callbacks and computed values, even create reducer infrastructure similar to having a mini-redux pipeline for use within your components.

  • Hooks

ReactDOM API

ReactDOM.render is the main function most people will ever use from the ReactDOM library. Pass it your React application's entry point, and a mount point in the DOM, and it will initialize your application, injecting it into that mount point.

  • ReactDOM.render

ReactDOM API

ReactDOM.renderToString is a useful function for server-side rendering. It will assemble a React application from its entry point, generating all of the markup comprising that component tree, and then result the HTML string itself, which is then typically returned in the response to the client. It includes additional data attributes used by React on the client, so that it can link up to the proper root node, add event listeners, etc. The ReactDOM.renderToNodeStream function returns the same output, but in a readable stream for piping to other processes or streaming to the client.

  • ReactDOM.renderToString
  • ReactDOM.renderToNodeStream

ReactDOM API

ReactDOM.hydrate is the partner function to React.renderToString and React.renderToNodeStream. Once the markup has been generated for your application, this is the function that uses the custom data attributes to wire up the React aspects and turn it from a web page into a web application.

  • ReactDOM.hydrate

ReactDOM API

ReactDOM.renderToStaticString is a useful function for pre-generating static markup pages. It outputs the same markup as React.renderToString, minus the custom data attributes that would normally be used by the client software to hook up event listeners and other behaviors. The ReactDOM.renderToStaticNodeStream function returns the same output, but in a readable stream for piping to other processes or streaming to the client.

  • ReactDOM.renderToStaticString
  • ReactDOM.renderToStaticNodeStream

Break! 10 Minutes!

Common Patterns

React authors tend to favor composition over inheritance. As a software pattern in general, composition tends to lend itself to looser coupling between modules, and better adherence to the ideals of the Single Responsible Principle. Components should be authored as discreet units, each performing some task that can be driven entirely by changes to state, and then composed into larger and larger components that know how best to orchestrate them for whatever task they in turn are written to do.

Composition Over Inheritance

Common Patterns

After praising the virtues of state-driven UI for so long, it seems obvious that some of the rendering you'll do is going to be conditional, based on application state at the time. A common form of this is to simply use the logical AND and OR operators, coupled with some JSX snippet, to conditionally include that snippet based on the truthiness or falsyness of some condition. If one needs separate snippets for both true and false conditions, a ternary is typically used. This makes it easy to colocate the conditions with their respective UI snippets, and still keeps the template pretty readable.

Conditional Rendering

Common Patterns

Creating lists of components is a fairly common task in React applications, and React fully supports arrays of components embedded in its views. Whether it's the result of some data being mapped over, or you're manually concatenating components to an ever-growing list based on some series of conditional checks, it doesn't matter. However, when rendering arrays of components, always specify a 'key' prop for each item in the list. The key should be unique for each item, and ideally, should be consistent from one render cycle to the next, as React uses it as a hint to determine when nodes are being created fresh vs just moved.

Arrays of Components

Common Patterns

Sometimes, the purpose of a component is simply to add decoration to other components that it's been fed. Libraries like Bootstrap provide a lot of foundational CSS structures for use in rapid-prototyping an application. A library like react-bootstrap might have a lot of the component implementations that just receive children, wrap them in some layer of DOM element, and then render the same children it was passed, with no transformations. When authoring your own components, make sure to think about where any passed children should be output, unless you're intentionally swallowing them.

Children Passthrough

Common Patterns

React offers two methods for dealing with form elements: controlled and uncontrolled. If you're familiar with Angular, controlled elements are synonymous with two-way binding. If you're not familiar with Angular, then controlled elements work by driving their inputs in a readonly fashion, off of some centralized, stateful value. When a 'change' event comes in for that component, the stateful value is updated, and then pushed back down to the input as the new readonly value. Uncontrolled components just track changes internally, and expect some other process to eventually harvest their values for submission.

Controlled vs Uncontrolled Components

Common Patterns

UI doesn't necessarily mean visual artifacts on the screen. UI could also involve audio components, or some kind of tactile feedback. We're not likely to be wearing haptic suits anytime soon, but components make an excellent mechanism for encapsulating traditionally imperative API calls as declarative components. The AudioContext API is one example of providing an audio vs visual component, another is the Vibration API for haptic feedback. All sorts of APIs can be handled with component wrappers, and since there's nothing to be rendered by them, you can simply return null, which is perfectly valid in React.

Non-visual Components

Common Patterns

Since composition is the prevailing pattern in us within React applications, it would make sense that in some situations, you'll be passing the same props down through a few layers of components. You don't need to explicitly state them all, and frankly, that would overcomplicate the API of the intermediate components on the route from ancestor to descendant. Just spread out the leftover props, and add or override them as needed by following the spread with your explicit prop definitions.

Spreading Props

Common Patterns

PropType Validation

PropTypes offer a way to validate the data type, shape, or anything else about the value of the props being passed to a component. The React production build allows the code behind this validation to be removed, to trim some of the fat on the output bundle, but during development, you should make use of these as much as possible.

Common Patterns

Merging Props

Sometimes, you may be receiving input props of the same name as some of your expected output props as you descend a component tree. Things like className, onClick, etc. are common enough that you'll encounter them quite often when composing component views. Rather than allowing one to override another, you should typically merge these so their values are aggregated.

Common Patterns

Self-binding Callbacks

When passing callbacks from a parent to child, make sure to determine whether they need access to the local instance scope. If so, they need to have that scope bound to them so they can setState or whatever else they need to do at a later time. You can do this with functionName.bind(this) in the constructor, or you can use an inline arrow function on the class body, to create and effectively self-bind the function in one step. Inline arrow functions on the class body are not standardized yet, but there is a babel plugin available to support that syntax now. Technically, there's a slight performance gain by binding in the constructor, due to making use of the prototype function, but a decorator function can help you get those gains along with the ease of binding on the class body.

Common Patterns

Hoisting State

In general, you want any shared state to reside at the lowest possible level that's still accessible to the components sharing it. That doesn't mean that it's always going to be manipulating that state directly, though. It could rely on one of its children to provide a button by which its state is updated, and in those cases, should pass callback props down to be attached where they're applicable. Remember, state should be passed down through the tree as props, and state updates should be passed up through the tree via callback props.

Common Patterns

React Instances as Props

Since props can literally hold any kind of data, there's nothing stopping you from passing instances of React elements to other components as props, and this is not an uncommon practice. It allows you, as an author, to provide some flexibility to how your component is rendered, by letting your consumers pass their own JSX snippets in as props.

Common Patterns

Service Components

Sometimes, you find yourself writing the same kinds of API connections or behaviors over and over from one component to the next. It would be better to extract this logic into its own component, which could then inform downstream components of changes they need to be aware of via updates to its own state. This kind of model can work well with resize operations, DOM observers, subscriptions to external services, or anything else you find yourself doing frequently enough to merit extracting and centralizing the logic.

Common Patterns

Higher-order Components

Some people prefer to use higher-order functions to apply repeatable actions to their components. In these cases, the higher-order functions return a new component class that handles the internal operation and applies it to the component class that was initially passed. The new component class that's returned is called a higher-order component. It's very similar to writing a service component, except with a higher-order component model, you don't need to add the service component wrapper everywhere you want to use it, you simply apply the function directly to the component in question.

Common Patterns

Passing Component Props

In much the same way as you might pass an instance of a component as a prop to some other component, the fact that React components can accept props of any type also means that you can pass a component class itself to be used by another component. Also, the fact that JSX is just a fancy way of making function calls to React.createElement means that for host components (e.g. div, span, etc), you can just use the string as a component 'class' reference. Pairing these bits of knowledge, you can set sane defaults of host components, and allow your consumers to override that as they wish, for more flexibility.

Common Patterns

Render Callbacks

The concept of render callbacks helps to combine facets from multiple other patterns into a powerful one that adds a lot of flexibility for consumers to layout their views however they like, with whatever markup and styles they want, while still allowing for some kind of behavioral governance by the parent component. It involves passing a function as the children of the component you're consuming, which will receive certain injected variables from the parent, and those variables can then be used in whatever layout you wish to write, with no preconceived layout being enforced by the parent.

Break! 10 Minutes!

Anti-Patterns

Prop Drilling

If you find yourself passing props from some ancestor component down to deeply-nested descendants somewhere, and having to continually forwarding them on from layer to layer to layer without using them anywhere in between, it's probably best to create a context provider for those data points. Prop drilling complicates the APIs of the components between ancestor and descendant, and makes your components less portable between views, as they're always reliant on all of their ancestor components forwarding those props down to them.

Anti-Patterns

Inline Arrow Functions as Props

There are conflicting schools of thought on this. For components with shallow trees of other components below them, and for host components, this isn't as important as in other cases. Passing inline arrow functions as props in your views means that those function references are being created fresh on each render cycle. This means React is having to do extra lifting unregistering the old one and registering the new one on each render, and recalculating its virtual DOM state unnecessarily on each render. Some consider this premature optimization, but if your application is significantly large, these can add up to real performance issues. Prebind your callbacks!

Anti-Patterns

Array Indexes as 'key' Props

The React 'key' prop is a special one, which gives React hints about which components have actually unmounted vs simply moved from one place in the DOM to another. If you're just moving an item, React can just reuse the existing DOM element handle vs destroying and recreating the element. If you have a large list of items, and are using an array index as the key, and some code snippet unshifts a new element onto the top of the array, every element will shift its position, get an updated array index, and React will destroy and rebuild them all, which can be expensive. Use unique, reliable identifiers for your keys, even if you have to compute them!

Anti-Patterns

Treating setState as a Synchronous Operation

The React setState function is called in a synchronous manner, so it's easy to assume that it also behaves in a synchronous manner. This is not true. The state of your 'state' object is not settled until a render cycle happens and it's flushed out to the component. React batches incoming calls to setState as a performance optimization, and then builds the new state object all at once, that means if you set state.total to 1, and run a loop from 1 to 10 to update it to state.total + 1 each time, when that loop completes, your new state.total value will be 2. There is a functional form of setState that allows you to receive the current pending state and update from it.

Anti-Patterns

Only Initializing (Not Updating) State From Props

If you're setting some local state based on some incoming prop value, in most situations, you should make sure it also accounts for changes in that incoming prop. People sometimes default their state values from props in the constructor, and then forget to write any logic that waits for updates and resyncs that state. In older versions, you would need to add a componentDidUpdate function to handle syncing state with prop updates, but in newer versions, you can use the static getDerivedStateFromProps function, which fires on both component mount and update.

Anti-Patterns

Using the componentWill* Lifecycle Functions

Early into React v16 and the move towards their Fiber-based architecture, the React team officially deprecated the use of the componentWill* functions (Mount, Update, ReceiveProps, etc). The purpose was to ensure that nobody was using them to potentially write code that might have side-effects (e.g. using componentWillReceiveProps to update state prior to the render cycle firing). React fibers need to have the option of prioritizing certain work units over others, or even canceling them outright, which isn't possible with side-effects in those lifecycle functions, or else it could lead to unstable application state and behaviors.

Anti-Patterns

Not Passing All 'constructor' Inputs to 'super()'

The constructor function in a React component receives more than just the props parameter, but props are usually the only one of those parameters that people need in the constructor. As such, most people tend to only receive the props parameter and pass it to the super function. As often as possible, you want to match the expected call signatures, if for no other reason than you don't know what the super class may or may not be doing with those extra parameters. When you use a React component constructor function, make sure to accept (with rest syntax) and pass (with spread syntax) all parameters up to the super class.

Anti-Patterns

Premature Optimization With Pure Components

The React PureComponent class automatically implements a shallow comparison function for your component's props, so that if they're strictly equal, it doesn't even bother trying to recalculate any virtual DOM at that layer or below it in the component tree. You may be tempted to start using it everywhere, but might find that it does more harm than good. If the majority of your components are attempting to update unnecessarily, PureComponent is great, but if that's not the case, you'll incur both the overhead of the shallow comparison and then the virtual DOM calculations on top of them.

Anti-Patterns

Mixing of React.PureComponent and Context

The shallow comparison function implemented in the PureComponent only checks for prop changes, and has no idea what context a component may be using. That means if the incoming props have not changed, but the context value has, React will still bail out on recalculating your virtual DOM state, and none of the updated context value will be reflected in your UI. If you run into this problem, consider wrapping your PureComponent itself in your context consumer, and passing the needed data points into it as props, so that if they change, the shallow comparison function will properly render the updates.

Anti-Patterns

Directly Manipulating the State Object

Do not make direct assignments to the state object in a React component, always use the setState function to perform updates to your component's state. Editing the object directly will not cause a render cycle to fire the way it does with setState, and it's just bad behavior in general.

Anti-Patterns

Monolithic Components

React development is all about creating small, reusable component modules: tightly cohesive, loosely coupled. These can then be composed in different ways to quickly build UI's for your application(s). Try to avoid letting your component modules get too large. If you start finding yourself breaking subsections of views out into their own functions to avoid your render function getting too large, that should probably a smell that your component is growing too large in general. Those functions should probably just be new components of their own, instead.

Resources

Parking Lot / Q&A

Training Opportunities

fn.

React: Uniting Companies Since 2019!

By Richard Lindsey

React: Uniting Companies Since 2019!

  • 262