July 19, 2022

Tutorials

React Container Components

There are different patterns for creating React apps. The Container Component pattern means separating data fetching and presentational parts of a component into two components. One component fetches data and the other one gets the data as props.

In the below example the PokemonsContainer component gets data from the api using axios and passes it to whatever the child of the container component is using the React.cloneElement method. so for example here the data state is being passed to children without doing that specifically in the component that PokemonsContainer exists:

// PokemonContainer.tsx

import React, { FC, ReactNode, useEffect, useState } from "react";
import { getPokemons } from "./api";

interface PokemonContainerProps {
  children: ReactNode;
}

const PokemonContainer: FC<PokemonContainerProps> = ({ children }) => {
  const [list, setList] = useState([]);

  useEffect(() => {
    (async () => {
      try {
        const pokemons = await getPokemons({ limit: 4 });
        setList(pokemons.data.results);
      } catch (err) {
        console.error(err);
      }
    })();
  }, []);

  return (
    <>
      {React.Children.map(children, (child) => {
        if (React.isValidElement(child)) {
          return React.cloneElement(child, { data: list });
        }

        return child;
      })}
    </>
  );
};

export default PokemonContainer;

And in the parent component we can put the component that we want to receive the data of PokemonContainer inside PokemonContainer as children:

// index.tsx
.
.
.
<PokemonContainer>
   <Pokemons />
</PokemonContainer>
.
.
.

Here the Pokemons component automatically receives the data from PokemonContainer as props and shows the list in an ordered list:

// Pokemons.tsx

import React, { FC } from "react";

interface PokemonsProps {
  data?: any[];
}

const Pokemons: FC<PokemonsProps> = ({ data }) => {
  let pokesList;
  if (data) {
    pokesList = data.map(({ name }, idx) => <li key={idx}>{name}</li>);
  }

  return pokesList ? <ol>{pokesList}</ol> : <small>No Items Found</small>;
};

export default Pokemons;

There are alternative ways to doing this (seperation of data fetching from representational part) like custom hooks and HOCs but the Container Component pattern is an important React design pattern that can be useful in some cases.