Container components are components that encapsulate the data loading and data management for the child application.

Let’s say you have a component called StarShipInfo component which lists the information about starship

const StarShipInfo = (id) => {
 const [starShip, setStarShip] = useState(null);
 useEffect(() => {
   const fetchStarShip = async () => {
     const response = await fetch(`https://swapi.dev/api/starships/${id}/`);
     const data = await response.json();
     setStarShip(data);
   };
   fetchStarShip();
 }, [id]);
 return (
   starShip && (
     <div>
       <p>Name: {starShip.name}</p>
       <p>Hyper Drive Rating: {starShip.hyperdrive_rating}</p>
       <p>Manufacturer: {starShip.manufacturer}</p>
       <p>Class: {starShip.starship_class}</p>
     </div>
   )
 );
};

By using container component pattern we can separate out the data fetching logic into a separate container. First, let’s create a separate component called StarShipInfoLoader. The purpose of this component is to create a fetch request, wait for the request to be completed, and then render the StarShipInfo component. The StarShipInfo component is passed as a children prop to this component, we access the passed component and render it with data.

const StarShipInfoLoader = ({ id, children }) => {
 const [starShip, setStarShip] = useState(null);
 useEffect(() => {
   const fetchStarShip = async () => {
     const response = await fetch(`https://swapi.dev/api/starships/${id}/`);
     const data = await response.json();
     setStarShip(data);
   };
   fetchStarShip();
 }, [id]);
 return (
   <>
     {starShip &&
       Children.map(children, (child) => {
         if (isValidElement(child)) {
           return cloneElement(child, { starShip });
         }
       })}
   </>
 );
};

This is how we can use the StarShipInfoLoader and StarShipInfo together.

 <StarShipInfoLoader id={id}>
   <StarShipInfo />
 </StarShipInfoLoader>