Presentational and Container Component Pattern in React

If you've been working with React for a while you'll have to agree that building responsive applications cleanly is difficult. As more components are added, the application grows more complex and things eventually get to a point where you will have to make important decisions such as where to put your data and how to manage state. Implementing the wrong decisions will not only make your application error prone, you will also have code that's hard to read and maintain. I'm going to discuss how container and presentational component patterns - two React patterns that are used in organizing React based applications.

Presentational Component Patterns can best be described as patterns that are primarily concerned with how things look. The primary function of a presentational component is to display data. They rarely handle state and are best written as stateless functional components. The term “presentational component” does not mean that the component is a type of class in the React library, it just implies a practice which programmers have used over time while creating component-based React user interfaces. Examples of presentational components are lists containing information and data. Check out this code block showing a school list as an example:

jsx
const SchoolList = props =>
  <ol>
    {props.schools.map(s => (
      <li>{s.name} - {s.grade}</li> 
    ))}
  </ol>

Although best written as stateless functional components, presentational components can be made to have a degree of interactivity via the addition of callbacks. Check out this example of how an input field can be controlled to have a certain limit of characters:

jsx
const milesLimit = 3;

export default function MilesRun({ onChange, limit }) {
  return (
    <input
      type=number
      className="miles-run"
      onChange={onChange}
      maxLength={limit || milesLimit} 
    />
  );
}

In the example, MilesRun fits the description of a presentational component. It displays data using the <input> tag, but then it also provides a class which we can use should we want to style our input and an onChange callback as well as a limit for the amount of numbers users can enter into the field. By providing all this definition in one place, the application is made easier to work with and debug.

  • Presentational components are primarily concerned with how things look.
  • Most times they contain no more than a render method.
  • Presentational components do not know how to load or alter the data that they render.
  • Presentational components rarely have any internally changeable state properties.
  • Presentational components best written as stateless functional components.

If presentational components are concerned with how things look, container components are more concerned with how things work. Container components are components that specify the data a presentational component should render. Instances of container components can be generated using higher order components such as connect() from Redux or createContainer() from Relay. One very important feature of components is their ability to be reused across different parts of an application. Check out the code block below:

jsx
import { useState, useEffect } from 'react';

export default function CarList() {
  const [cars, setCars] = useState([]);

  useEffect(() => {
    getCars(cars => setCars(cars));
  }, []);
 
  return (
    <ul>
      {this.state.cars.map(e => (
        <li>{e.make}: {e.model}</li>
      ))}
    </ul>
  );
}

In the example above, we've made the CarList component responsible for fetchig and displaying of data but there's a catch, CarList cannot be used unless under the same conditions. Let's implement the container component pattern and solve that problem:

jsx
import { useState, useEffect } from 'react';

export default function CarListContainer() {
  const [cars, setCars] = useState([]);

  useEffect(() => {
    getCars(cars => setCars(cars));
  }, []);

  return <CarsList cars={this.state.cars} />;
}

We then recreate CarsList to take cars as a prop:

jsx
const CarsList = props =>
  <ul>
    {props.cars.map(e => (
      <li>{e.make}: {e.model}</li>
    ))}
  </ul>

By setting apart our data fetching and rendering operations, we have made our CarsList component super reusable. Not only that, we get go understand our application's user interface better and make our components easier to understand.

  • Container components have to do with with how things work.
  • They may contain presentational components. Presentational components don't contain container components.
  • Provides data and behavior to presentational components and other container components.
  • Because they are mostly data sources, they are often stateful.

Now that we have seen how presentational and container components work, let's show how we can make our components and applications in general look more presentable using both patterns. Below is a component which is yet to be split into container and presentational:

jsx
export default function Clock({ startTime }) {
  const [time, setTime] = useState(startTime);
  const [interval, setInterval] = useState();

  useEffect(() => {
    setInterval(updateTime, 1000);
    return () => clearInterval(interval);
  }, []);

  function formatTime(time) {
    const [hours, minutes, seconds] = [
      time.getHours(),
      time.getMinutes(),
      time.getSeconds()
    ].map(num => num < 10 ? '0' + num : num);

    return { hours, minutes, seconds };
  }

  function updateTime() {
    setTime(new Date(time.getTime() + 1000));
  }

  const time = formatTime(time);
  
  return <h1>{time.hours} : {time.minutes} : {time.seconds}</h1>;
};

ReactDOM.render(<Clock time={new Date()}/>, ...);

In the constructor of the Clock component above, the passed time object is stored to the internal state. setInterval updates the state every second and re-renders the component. However our Clock component may have a couple of problems, changing the time inside the component implies that only Clock knows the time value. Should there be another component that requires this data, it will be very hard to access it.

Let's try to rebuild the Clock component into a presentational and a container component. We first create a container component, ClockContainer:

jsx
// Clock/index.js
import Clock from './Clock.js'; // our presentational component

export default function ClockContainer({ startTime }) {
  const [time, setTime] = useState(startTime);
  const [interval, setInterval] = useState();

  useEffect(() => {
    setInterval(updateTime, 1000);
    return () => clearInterval(interval);
  }, []);

  function extract(time) {
    return {
      hours: time.getHours(),
      minutes: time.getMinutes(),
      seconds: time.getSeconds()
    };
  }

  function updateTime() {
    setTime(new Date(time.getTime() + 1000));
  }

  return <Clock { ...extract(this.state.time) }/>;
};

Our ClockContainer component above accepts timeStart, performs the setInterval loop and knows details about the data (getHours, getMinutes and getSeconds).

Our presentational component will consist of just the visual representation of our clock:

jsx
// Clock/Clock.js
export default function Clock({ hours, minutes, seconds }) {
  // pad the number with a 0 if needed
  const [hours, minutes, seconds] = [
    hours,
    minutes,
    seconds
  ].map(num => num < 10 ? '0' + num : num);

  return <h1>{hours} : {minutes} : {seconds}</h1>;
};

By dividing our initial Clock component in this fashion, we are able to reuse it easily in different instances. This way Clock.js can be present in applications that have nothing to do with keeping time or date.

Want to improve your coding and design skills?

I'm continually researching the best practices and tools for coding.
Join 50,000+ developers looking to make cool stuff.

We value your privacy. 1-click unsubscribe.

Comments

What did you think of the article? Let us know!
(these comments are powered by GitHub issues and use 0 trackers)