React Design Patterns (Part 2)

React Design Patterns (Part 2)

EngineeringJavaScriptReact
Fix bugs faster! Log Collection Made Easy
START NOW!

This article is the second part of the React Design Patterns article. If you missed the first part, go to part 1 (link here) of the series.

This time we’ll be talking about the Context pattern, the Presentational and Container Components pattern, and the Compound Components pattern.

Context

According to the React documentation:

Context provides a way to pass data through the component tree without having to pass props down manually at every level.

In simple terms, If you have a global state that needs to pass through several component levels, you can use Context. For example: if you have a theme that influences all the components, Context will streamline the process.

Note. There is one potential snag to bear in mind when using Context: it can make the components less reusable. The Context data will be available in the Provider scope, so you can’t use it outside the Provider. I found a great video that explains this issue and tells you how to avoid ‘prop drilling.’

Let’s see an example of Context in action:

import React, { useContext, createContext } from "react";
import "./styles.css";
let data = {
  title: "Welcome"
};
const Context = createContext();

export default function App() {
  return (
    <Context.Provider value={data}>
      <div className="App">
        <Card />
      </div>
    </Context.Provider>
  );
}

const Card = () => {
  return (
    <div className="card">
      <CardItem />
    </div>
  );
};

const CardItem = () => {
  return (
    <div className="CardItem">
      <Title />
    </div>
  );
};

const Title = () => {
  const data = useContext(Context);
  return <h1>{data.title}</h1>;
};

As we can see in this (elementary) example, we have three levels of components, and we only use the data.title in the last level. This way, we don’t need to pass the props to all the levels.

A few tips on context syntax

I always apply this syntax when using context. However, there are some things I found out when I wrote it again:

  • In the case of “static data” (like the example), we actually don’t need the Provider. we can fulfill that function ourselves:
let data = {
  title: "Welcome"
};
const Context = createContext(data);

export default function App() {
  return (
    <div className="App">
      <Card />
    </div>
  );
}

At the other end of the scale, we can use the Customer instead of useContext, like this:

const Title = () => {
  return (<Context.Consumer>
            {(data) => <h1>{data.title}</h1>}
        </Context.Consumer>);
};

Presentational and Container Components

These components (also known as Smart And Dumb Components) are among the best-known React patterns. There are no references to them in the React documentation, but Dan Abramov’s article provides an excellent guide.

In simple terms, Presentational And Container Components refer to the separation of the business logic components from the UI views.

Let’s look at another scenario:

  • We need to build a Card component.
  • Inside the card, we have three other components: TitleImage and Button.
  • The button changes the picture after a click on it.

Before we start working on our components, let’s create two folders: ‘Presentational’ and ‘Container.’ Now, let’s build the three Presentational components :

Title.js :

import React from "react";
export default function Title(props) {
  const { children, ...attributes } = props;
  return <h1 {...attributes}>{children}</h1>;
}

Image.js :

import React from "react";
export default function Image(props) {
  const { src, alt } = props || {};
  return <img src={src} alt={alt} />;
}

Button.js :

import React from "react";
export default function Button(props) {
  const { children, ...attributes } = props;
  return <button {...attributes}>{children}</button>;
}

Finally, we can build in the Container folder component, known as the Card.

Card.js :

import React, { useEffect, useState } from "react";
import Title from "../Presentational/Title";
import Image from "../Presentational/Image";
import Button from "../Presentational/Button";

export default function Card() {
  const [card, setCard] = useState({});
  const [srcIndex, setSrcIndex] = useState(0);

  useEffect(() => {
    setCard({
      title: "Card Title",
      image: {
        imagesArray: [
          "<https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTh87QN4DkF7s92IFSfm7b7S4IR6kTdzIlhbw&usqp=CAU>",
          "<https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRjFnHdaH1i1m_xOaJfXTyq4anRFwRyCg1p1Q&usqp=CAU>"
        ],
        alt: "card image"
      }
    });
  }, []);
  const { image } = card;
  const changeImage = () =>
    setSrcIndex((index) =>
      index < image.imagesArray.length - 1 ? index + 1 : index - 1
    );
  return (
    <div className="card">
      <Title className="title-black">{card.title && card.title}</Title>
      <Image
        src={image && image.imagesArray[srcIndex]}
        alt={image && image.alt}
      />
      <Button onClick={changeImage}>Change Picture</Button>
    </div>
  );
}

If you want to see the full code, check it out here.

Note! Many of you may be wondering why you needed to separate into different components. You could just write them inside Card, right?

Well when we separate the components, we can reuse them anywhere. But even more importantly, it is much easier to implement other patterns like HOC or Render Props.

Compound Components

In my opinion, this is one of the most intricate patterns to understand, but I will try to explain it as simply as I can.

When we talk about Compound Components, the most simple way is to think about select and option in HTML. You can look at them as a group of components that have a basic functionality. There are states that are managed globally (like in the context pattern) or from the container (like in presentational and container patterns).

Compound components are really a mixture of these two. It’s almost as if they each have their owned states and they manage them from within.

Let’s look at the next scenario:

  • We need to develop Select and Option components.
  • We want the Option to be vivid, with different colors.
  • The Option color will influence the Select color.

Let’s see the example:

App.js

import React from "react";
import "./styles.css";
import Select from "./Select";
import Option from "./Option";

export default function App() {
  return (
    <div>
      <Select>
        <Option.Blue>option 1</Option.Blue>
        <Option.Red>option 2</Option.Red>
        <Option>option 3</Option>
      </Select>
    </div>
  );
}
  • The App renders the Select and the Option components.
  • Option.Blue and Option.Red are ‘colors components.’

Option.js

sdsdimport React, { useEffect } from "react";

function Option(props) {
  const { children, style, value, setStyle } = props;
  useEffect(() => {
    if (setStyle) setStyle({ backgroundColor: style.backgroundColor });
  }, [setStyle, style]);
  return (
    <option value={value} style={style}>
      {children}
    </option>
  );
}

Option.Blue = function (props) {
  props.style.backgroundColor = "blue";
  return Option(props);
};

Option.Red = function (props) {
  props.style.backgroundColor = "red";
  return Option(props);
};
export default Option;
  • Here you can see the Implementation of Option.Blue and Option.Red. As will be apparent, we render the Option component and just add a property to props.
  • The setStyle comes from Select. It’s for changing the select color to the color of the selected option.

Select.js

import React, { useState } from "react";

export default function Select(props) {
  const { children } = props;
  const [style, setStyle] = useState({});

  const findOptionActive = (e) => {
    const index = e.target.value * 1;
    const optionStyle = { ...e.nativeEvent.target[index].style };
    if (optionStyle) setStyle({ backgroundColor: optionStyle.backgroundColor });
  };

  const childrenWithProps = React.Children.map(children, (child, index) => {
    return React.cloneElement(child, {
      ...child.props,
      value: index,
      setStyle:
        index === 0 && Object.keys(style).length === 0 ? setStyle : null,
      style: { backgroundColor: "white" }
    });
  });

  return (
    <select onChange={findOptionActive} style={style}>
      {childrenWithProps}
    </select>
  );
}
  • Now, we have a select function with the attributes of onChange style.
  • findOptionActive gets the style of the option and changes the style of select accordingly,
  • The magic really happens in childrenWithProps. Normally, when Select receives children, we can’t access the child props – but with the help of React.Children and React.cloneElement we can do it. As you can see, we can pass valuesetStyle, and style as props.

To get the Full Code, click here.

This exercise gives you good practice, and if you want to try it yourself (maybe in another pattern), add your solution in a comment below.

Conclusion

This article was intended to show you different patterns in React. You don’t need to use any of the patterns if you don’t want to, but it’s good for a developer to know design patterns on any framework or language, to understand different syntax levels when they see a new codebase.

I hope you enjoyed the tutorial and learned something new. If you know any other pattern or have further information on any of the topics mentioned in the article, please add a comment below.

Before we go

Bugfender is a tool that helps you finding errors in your production apps. We strongly believe in sharing knowledge and that’s why we create articles like this one. If you liked it, help us to continue creating content by sharing this article or signing up in Bugfender.

Expect the Unexpected! Debug Faster with Bugfender
START FOR FREE

Trusted By

/assets/images/svg/customers/highprofile/intel.svg/assets/images/svg/customers/projects/safedome.svg/assets/images/svg/customers/highprofile/gls.svg/assets/images/svg/customers/cool/domestika.svg/assets/images/svg/customers/highprofile/volkswagen.svg/assets/images/svg/customers/projects/menshealth.svg/assets/images/svg/customers/cool/napster.svg/assets/images/svg/customers/highprofile/deloitte.svg

Already Trusted by Thousands

Bugfender is the best remote logger for mobile and web apps.

Get Started for Free, No Credit Card Required