React Patterns on GitHub #

Get the latest React patterns, tips, and tricks right to your inbox.

Contents #

Translations #

These translations are not verified and links are not endorsements.

Chinese

Element #

Elements are anything inside angle brackets.

<div></div>
<Greeting />

Components return Elements.

Component #

Define a Component by declaring a function that returns a React Element.

function Greeting() {
  return <div>Hi there!</div>;
}

Expressions #

Use curly braces to embed expressions in JSX.

function Greeting() {
  let name = "chantastic";

  return <div>Hi {name}!</div>;
}

Props #

Take props as an argument to allow outside customizations of your Component.

function Greeting(props) {
  return <div>Hi {props.name}!</div>;
}

defaultProps #

Specify default values for props with defaultProps.

function Greeting(props) {
  return <div>Hi {props.name}!</div>;
}
Greeting.defaultProps = {
  name: "Guest",
};

Destructuring props #

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

Think of it like the opposite of literal assignment.

let person = { name: "chantastic" };
let { name } = person;

Works with Arrays too.

let things = ["one", "two"];
let [first, second] = things;

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

function Greeting(props) {
  return <div>Hi {props.name}!</div>;
}

function Greeting({ name }) {
  return <div>Hi {name}!</div>;
}

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

function Greeting({ name, ...restProps }) {
  return <div>Hi {name}!</div>;
}

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>.

function Greeting({ name, ...restProps }) {
  return <div {...restProps}>Hi {name}!</div>;
}

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

<Greeting name="Fancy pants" className="fancy-greeting" id="user-greeting" />

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.

function Greeting({ name, ...platformProps }) {
  return <div {...platformProps}>Hi {name}!</div>;
}

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.

function MyButton(props) {
  return <button className="btn" {...props} />;
}

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

<MyButton className="delete-btn">Delete...</MyButton>

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.

function MyButton(props) {
  return <button {...props} className="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 a space.

function MyButton({ className, ...props }) {
  let classNames = ["btn", className].join(" ");

  return <button className={classNames} {...props} />;
}

To guard from undefined showing up as a className, you could update your logic to filter out falsy values:

function MyButton({ className, ...props }) {
  let classNames = ["btn", className].filter(Boolean).join(" ").trim();

  return <button className={classNames} {...props} />;
}

Bear in mind though that if an empty object is passed it'll be included in the class as well, resulting in: btn [object Object].

The better approach is to make use of available packages, like classnames or clsx, that could be used to join classnames, relieving you from having to deal with it manually.

Conditional rendering #

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

if #

{
  condition && <span>Rendered when `truthy`</span>;
}

unless #

{
  condition || <span>Rendered when `falsy`</span>;
}

if-else #

{
  condition ? (
    <span>Rendered when `truthy`</span>
  ) : (
    <span>Rendered when `falsy`</span>
  );
}

Children types #

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

String #

<div>Hello World!</div>

Array #

<div>{["Hello ", <span>World</span>, "!"]}</div>

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.

<ul>
  {["first", "second"].map((item) => (
    <li>{item}</li>
  ))}
</ul>

That's equivalent to providing a literal array.

<ul>{[<li>first</li>, <li>second</li>]}</ul>

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

<ul>
  {arrayOfMessageObjects.map(({ id, ...message }) => (
    <Message key={id} {...message} />
  ))}
</ul>

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.

const Width = ({ children }) => children(500);

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.

<Width>{(width) => <div>window is {width}</div>}</Width>

We get this output.

<div>window is 500</div>

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

<Width>
  {(width) => (width > 600 ? <div>min-width requirement met!</div> : null)}
</Width>

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

const MinWidth = ({ width: minWidth, children }) => (
  <Width>{(width) => (width > minWidth ? children : null)}</Width>
);

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

class WindowWidth extends React.Component {
  constructor() {
    super();
    this.state = { width: 0 };
  }

  componentDidMount() {
    this.setState({ width: window.innerWidth }, () =>
      window.addEventListener("resize", ({ target }) =>
        this.setState({ width: target.innerWidth })
      )
    );
  }

  render() {
    return this.props.children(this.state.width);
  }
}

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.

class SomeContextProvider extends React.Component {
  getChildContext() {
    return { some: "context" };
  }

  render() {
    // how best do we return `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.

// option 1: extra div
return <div>{children}</div>;

// option 2: unhelpful errors
return children;

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

return React.Children.only(this.props.children);

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".

<button type="button">

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

const Button = props =>
  <button type="button" {...props}>

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

<Button />
// <button type="button"><button>

<Button className="CTA">Send Money</Button>
// <button type="button" class="CTA">Send Money</button>

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.

<button type="button" className="btn btn-primary">

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

import classnames from "classnames";

const PrimaryBtn = (props) => <Btn {...props} primary />;

const Btn = ({ className, primary, ...props }) => (
  <button
    type="button"
    className={classnames("btn", primary && "btn-primary", className)}
    {...props}
  />
);

It can help to visualize this.

PrimaryBtn()
  ↳ Btn({primary: true})
    ↳ Button({className: "btn btn-primary"}, type: "button"})
      ↳ '<button type="button" class="btn btn-primary"></button>'

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

<PrimaryBtn />
<Btn primary />
<button type="button" className="btn btn-primary" />

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.

handleClick(e) { /* do something */ }

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.

handleClick() { require("./actions/doStuff")(/* action stuff */) }
handleMouseEnter() { this.setState({ hovered: true }) }
handleMouseLeave() { this.setState({ hovered: false }) }

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

handleEvent({type}) {
  switch(type) {
    case "click":
      return require("./actions/doStuff")(/* action dates */)
    case "mouseenter":
      return this.setState({ hovered: true })
    case "mouseleave":
      return this.setState({ hovered: false })
    default:
      return console.warn(`No case for event type "${type}"`)
  }
}

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

<div onClick={() => someImportedAction({ action: "DO_STUFF" })}

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.

<HorizontalSplit
  startSide={<SomeSmartComponent />}
  endSide={<AnotherSmartComponent />}
/>

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.

class HorizontalSplit extends React.Component {
  shouldComponentUpdate() {
    return false;
  }

  render() {
    return (
      <FlexContainer>
        <div>{this.props.startSide}</div>
        <div>{this.props.endSide}</div>
      </FlexContainer>
    );
  }
}

Container component #

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

Given this reusable CommentList component.

const CommentList = ({ comments }) => (
  <ul>
    {comments.map((comment) => (
      <li>
        {comment.body}-{comment.author}
      </li>
    ))}
  </ul>
);

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

class CommentListContainer extends React.Component {
  constructor() {
    super()
    this.state = { comments: [] }
  }

  componentDidMount() {
    $.ajax({
      url: "/my-comments.json",
      dataType: 'json',
      success: comments =>
        this.setState({comments: comments});
    })
  }

  render() {
    return <CommentList comments={this.state.comments} />
  }
}

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