Patterns

Source: https://reactpatterns.com/

React Patterns on GitHub

Contents

Function component

The simplest way to declare reusable components.

They're just functions.

Collect props from the first argument of your function.

Define any number of local variables to do what you need in your function components. Always return your React Component at the end.

Set defaults for any required props using defaultProps.

Destructuring props

Destructuring assignment is a JavaScript feature. It was added to the language in ES2015. So it might not look familia

Think of it like the opposite of literal assignment.

Works with Arrays too.

Destructuring assignment is used a lot in function components. These component declarations below are equivalent.

There's a syntax for collecting remaining props into an object. It's called rest parameter syntax and looks like this.

Those three dots (...) take all the remaining properties and assign them to the object restProps.

So, what do you do with restProps once you have it? Keep reading...

JSX spread attributes

Spread Attributes is a feature of JSX. It's a syntax for providing an object's properties as JSX attributes.

Following the example from Destructuring props, We can spread restProps over our <div>.

This makes Greeting super flexible. We can pass DOM attributes to Greeting and trust that they'll be passed through to div.

Avoid forwarding non-DOM props to components. Destructuring assignment is popular because it gives you a way to separate component-specific props from DOM/platform-specific attributes.

Merge destructured props with other values

Components are abstractions. Good abstractions allow for extension.

Consider this component that uses a class attribute for style a button.

This works great until we try to extend it with another class.

In this case, delete-btn replaces btn.

Order matters for JSX spread attributes. The props.className being spread is overriding the className in our component.

We can change the order but now the className will never be anything but btn.

We need to use destructuring assignment to get the incoming className and merge with the base className. We can do this simply by adding all values to an array and joining them with an space.

To guard from undefined showing up as a className, Use default values.

Conditional rendering

You can't use if/else statements inside a component declarations. So conditional (ternary) operator and short-curcuit evaluation are your friends.

if

unless

if-else

Children types

React can render children from most types. In most cases it's either an array or a string.

String

Array

Array as children

Providing an array as children is a very common. It's how lists are drawn in React.

We use map() to create an array of React Elements for every value in the array.

That's equivalent to providing a literal array.

This pattern can be combined with destructuring, JSX Spread Attributes, and other components, for some serious terseness.

Function as children

React components don't support functions as children. However, render props is a pattern for creating components that take functions as children.

Render prop

Here's a component that uses a render callback. It's not useful, but it's an easy illustration to start with.

The component calls children as a function, with some number of arguments. Here, it's the number 500.

To use this component, we give it a function as children.

We get this output.

With this setup, we can use this width to make rendering decisions.

If we plan to use this condition a lot, we can define another components to encapsulate the reused logic.

Obviously a static Width component isn't useful but one that watches the browser window is. Here's a sample implementation.

Many developers favor Higher Order Components for this type of functionality. It's a matter of preference.

Children pass-through

You might create a component designed to apply context and render its children.

You're faced with a decision. Wrap children in an extraneous <div /> or return children directly. The first options gives you extra markup (which can break some stylesheets). The second will result in unhelpful errors.

It's best to treat children as an opaque data type. React provides React.Children for dealing with children appropriately.

Proxy component

(I'm not sure if this name makes sense)

Buttons are everywhere in web apps. And every one of them must have the type attribute set to "button".

Writing this attribute hundreds of times is error prone. We can write a higher level component to proxy props to a lower-level buttoncomponent.

We can use Button in place of button and ensure that the typeattribute is consistently applied everywhere.

Style component

This is a Proxy component applied to the practices of style.

Say we have a button. It uses classes to be styled as a "primary" button.

We can generate this output using a couple single-purpose components.

It can help to visualize this.

Using these components, all of these result in the same output.

This can be a huge boon to style maintenance. It isolates all concerns of style to a single component.

Event switch

When writing event handlers it's common to adopt the handle{eventName} naming convention.

For components that handle several event types, these function names can be repetitive. The names themselves might not provide much value, as they simply proxy to other actions/functions.

Consider writing a single event handler for your component and switching on event.type.

Alternatively, for simple components, you can call imported actions/functions directly from components, using arrow functions.

Don't fret about performance optimizations until you have problems. Seriously don't.

Layout component

Layout components result in some form of static DOM element. It might not need to update frequently, if ever.

Consider a component that renders two children side-by-side.

We can aggressively optimize this component.

While HorizontalSplit will be parent to both components, it will never be their owner. We can tell it to update never, without interrupting the lifecycle of the components inside.

Container component

"A container does data fetching and then renders its corresponding sub-component. That’s it."—Jason Bonta

Given this reusable CommentList component.

We can create a new component responsible for fetching data and rendering the CommentList function component.

We can write different containers for different application contexts.

Higher-order component

A higher-order function is a function that takes and/or returns a function. It's not more complicated than that. So, what's a higher-order component?

If you're already using container components, these are just generic containers, wrapped up in a function.

Let's start with our Greeting component.

If it gets props.name, it's gonna render that data. Otherwise it'll say that it's "Connecting...". Now for the the higher-order bit.

This is just a function that returns component that renders the component we passed as an argument.

Last step, we need to wrap our our Greeting component in Connect.

This is a powerful pattern for providing fetching and providing data to any number of function components.

State hoisting

function-component don't hold state (as the name implies).

Events are changes in state. Their data needs to be passed to stateful container components parents.

This is called "state hoisting". It's accomplished by passing a callback from a container component to a child component.

Name receives an onChange callback from NameContainer and calls on events.

The alert above makes for a terse demo but it's not changing state. Let's change the internal state of NameContainer.

The state is hoisted to the container, by the provided callback, where it's used to update local state. This sets a nice clear boundary and maximizes the re-usability of function component.

This pattern isn't limited to function components. Because function components don't have lifecycle events, you'll use this pattern with component classes as well.

Controlled input is an important pattern to know for use with state hoisting

(It's best to process the event object on the stateful component)

Controlled input

It's hard to talk about controlled inputs in the abstract. Let's start with an uncontrolled (normal) input and go from there.

When you fiddle with this input in the browser, you see your changes. This is normal.

A controlled input disallows the DOM mutations that make this possible. You set the value of the input in component-land and it doesn't change in DOM-land.

Obviously static inputs aren't very useful to your users. So, we derive a value from state.

Then, changing the input is a matter of changing component state.

This is a controlled input. It only updates the DOM when state has changed in our component. This is invaluable when creating consistent UIs.

If you're using function components for form elements, read about using state hoisting to move new state up the component tree.

Last updated