Notes on React Component Patterns

For Intermediate React Developers

These notes identify and examine common React component patterns.

1 : React Container/Smart/Controller Component Pattern

This section will outline the React Component Container/Smart/Controller Pattern.

1.1 - What is the Container/Smart/Controller Component Pattern?

You will hear this pattern called container components, smart components, or controller components/views. Basically, when you wrap a component around presentational/dumb/stateless component(s) and, the outer component deals only with setting and getting data/state/store stuff, that is then pass down to the presentational/dumb/stateless components via props/context, you are dealing with a container/smart/controller component pattern.

1.2 - Example(s)

Below are two sudo components contrasting the difference between a container/smart/controller component and presentational/dump/stateless component

CommentList.js is a presentational/dump/stateless component (i.e. it takes data in and displays it):


import React from "react";

const Commentlist = ({comments}) => (
  <ul>
    {comments.map(({ body, author }) =>
      <li>{body}-{author}</li>
    )}
  </ul>
)
          

CommentListContainer.js is a container/smart/controller component (it manages data and sends it to other components for display):


import React from "react";
import CommentList from "./CommentList";

class CommentListContainer extends React.Component {
  state = { comments: [] }
  
  componentDidMount() {
    fetch("/my-comments.json")
      .then(res => res.json())
      .then(comments => this.setState({ comments }))
  }
  
  render() {
    return <CommentList comments={this.state.comments} />;
  }
}
          

The code example below is another example of the container/smart/controller component:

The container/smart/controller <CounterContainer> component manages the state while the dump/stateless <Count> and <Button> components consume state related concerns from the container component above it via props.

1.3 - When do I use the pattern?

  • When you want your presentational/dump/stateless components to be reusable and disconnect/separated from data/state/store concerns.
  • You want to create simple tests for UI components. A presentational/dump/stateless components is dead simple to test as they typically have no state and only take props.

2 : High-Order Components Pattern

This section will outline the React High-Order Component Pattern.

2.1 - What is the High-Order Components Pattern?

A higher order component is a javascript function that takes a React component as an argument, wraps the passed component with another component (i.e. a Container/Smart/Controller Component Pattern) and then returns this wrapped component.

2.2 - Example(s)

Imagine you want several components to share the same exact prop. This can be done by creating a HOC to wrapped any component so it always gets exactly the same prop (Note: would work with state too, not just props).

HOC (withReactNameProp.js):


import React from "react";

function withReactNameProp(WrappedComponent) {
  // this component wraps the component passed in i.e. WrappedComponent
  return class extends React.Component {
    // give it name in component tree otherwise name is generic "_class"
    static displayName = "withReactNameProp";
    render() {
      return <WrappedComponent name="React" {...this.props} />;
    }
  };
}

export default withReactNameProp;

Component to be wrapped (Hello.js):


import React from "react";
import withReactNameProp from "./withReactNameProp";

// foo comes from <Hello foo="bar">
// the name "React" comes from HOC
const Hello = ({ name, foo }) => {
  return (
    <>
      {foo} {/* prints "bar" works because of {...this.props} in HOC */}
      <h1>Hello {name}!</h1> {/* prints "React" works because HOC passes name prop */}
    </>
  );
};

// Hello becomes <withReactNameProp><Hello /></withReactNameProp>
export default withReactNameProp(Hello);

Using the Wrapped Component (index.js):


import React from "react";
import ReactDOM from "react-dom";

import Hello from "./Hello";

// <Hello /> is really <withReactNameProp><Hello /></withReactNameProp>
ReactDOM.render(<Hello foo="bar" />, document.getElementById("root"));

2.3 - When do I use the patten?

When you want to share the details of component with any number of other components. A common use case is sharing the same state setup with any number of components so each instance will have its own state setup but you only have to define the state setup once (i.e. the HOC pattern is a DRY'ing (don't repeat yourself) technique so you can reuses parts of program without duplicating the program over and over).

Below is an example of using this pattern to share state for <Counter />:

3 : Children Props Pattern

This section will outline the Children Props Pattern.

3.1 - What is the Children Props Pattern?

The props value passed to React components has a special value on it called props.children. The children value is the child value of the component when the component is used (e.g. <Component> this is the children value here </Component>).

The react documentation explains props.children as, "props.children is available on every component. It contains the content between the opening and closing tags of a component.".

If you think about it the children value of a component can be any type of JavaScript value not just strings, components, or JSX (e.g. could be a function too or really any javascript expression. Keep this in mind when you look at the Render Props Pattern).

In the code example below what do you think is the text shown on the <Button> when it is viewed in the UI (i.e., "delete" or "I'm available for composition"?)


import React from "react";
import ReactDOM from "react-dom";

const Button = () => {
  return <button>delete</button>;
};

ReactDOM.render(
  <Button>I'm available for composition</Button>,
  document.getElementById("root")
);

If you said "delete" then you are correct. But what if we didn't wanted the buttons text to be hard coded inside of the <Button>? What if we wanted to pass the button's text when the component is used (i.e. making the <Button> reusable). To do this we could use props, but we could also use props.children which contains:, "the content between the opening and closing tags of a component".


import React from "react";
import ReactDOM from "react-dom";

const Button = (props) => {
  return <button>{props.children}</button>;
};

ReactDOM.render(
  <Button>delete</Button>,
  document.getElementById("root")
);

Now the <Button> will always show the child content passed to the <Button> (this facilitates composition)

If the props.children value was not available then composition would all have to happen via props alone. Which could get really unwieldy and ugly fast if you have a lot composition going on (i.e. a lof other components as children). So props.children can be used when you need to output the contents of a component to the UI.

3.3 - Example(s)

The example below contrast both props and props.children composition:


function Posts(props){
  return (
    <div className="posts">
      {props.children}
    </div>
  )

}

function Share(props){
  return (
    <div>
      <button>{props.name}</button>
    </div>
  )
}


<Posts> {/* Post wraps the JSX and Share components */}
    <h1>this my heading</h1>
    <p>this is big old post</p>
    <Share name="facebook"/> {/* uses props but could have used props.children */}
    <Share name="twitter"/> {/* uses props but could have used props.children */}
</Posts>

3.2 - When do I use the patten?

  • When you don't know what the children will be for a component ahead of time, you can use children to get access to the value when the component is used, not defined (e.g. <Button>button text</Button>).
  • When you need to output the contents of a component to the UI (e.g. <Modal title="alert"> <Alert> the site is down </Alert> </Modal>).

4 : Render Props Pattern

This section will outline the React Render Props Pattern.

4.1 - What is the Render Props Pattern?

The render props pattern involves passing a function, that returns a component/JSX, via props to a component that then invokes that function, providing it with arguments (typically the arguments have things you want to reuse).

4.2 - Example(s)

Imagine you want several components to share the same value. This can be done by using a render prop on the Component containing the value you want to share. For example, what if you wanted to share the value { name: "react" } with two or more components and you don't want to repeat yourself.


import React from "react";
import ReactDOM from "react-dom";

// create a component that shares stuff e.g. { name: "react" }
const NameReactViaRenderProp = props => {
  // could share state using class component or hooks too
  return props.render({ name: "react" });
};

// Create a Hello component that share {name: 'react'}
// Really {name: 'react'} could be any value
const Hello = () => {
  return (
    <NameReactViaRenderProp
      render={({ name }) => {// render prop
        // a function passed to a component via props
        return <h1>Hello {name}!</h1>;
      }}
    />
  );
};

ReactDOM.render(
  <>
    <Hello /> <Hello />
  </>,
  document.getElementById("root")
);
  

Not unlike the React HOC pattern the Render Props Pattern is used to reuses the details of another component by wrapping a container component around the a dumb/presentation/stateless component.

Note that one does see a similar pattern using only props.children:


import React from "react";
import ReactDOM from "react-dom";

// create a component that shares stuff e.g. { name: "react" }
const NameReactViaPropsChildren = (props) => {
  // could share state using class component or hooks too
  return props.children({ name: "react" });
};

// Create a Hello component that uses {name: 'react'}
// Reacly {name: 'react'} could be any value
const Hello = () => {
  return (
    <NameReactViaPropsChildren>
    {/* a function passed to a component via props.children */}
      {({ name }) => {
        return <h1>Hello {name}!</h1>;
      }}
    </NameReactViaPropsChildren>
  );
};

ReactDOM.render(
  <>
    <Hello /> <Hello />
  </>,
  document.getElementById("root")
);

4.3 - When do I use the patten?

When you want to re-use the details of component with any number of other components. A common use case is sharing the same state setup with any number of components so each instance will have its own state setup but you only have to define the state setup once (i.e. the Render Props pattern is a code DRY'ing (don't repeat yourself) technique so you can reuses parts of program without duplicating the program over and over).

Below is an example of using this pattern to share state for <Counter />: