DEV Community

Tomer Raitz
Tomer Raitz

Posted on • Originally published at bugfender.com

React Design Patterns (Part 1)

alt text

This article originally appeared at bugfender.com: React Design Patterns (Part 1).

Having studied React for several months, one of the subjects I've paid particularly close attention to is design patterns. In this article, I'll share my key findings.

Note: Some of the patterns focus on state management concepts, but we can avoid Redux, Mobx and other third-party state management tools because they're not related to the subject of this article.

Render Props

Abounding to React docs:

The term “render prop” refers to a technique for sharing code between React components using a prop whose value is a function.

In simple words, it's just a prop with a function value. The function is a component that needs to be rendered. Maybe you've seen it in React Router:

<Route
  path='/about'
  render={(props) => (
    <About {...props} isLoad={true} />
  )}
/>
Enter fullscreen mode Exit fullscreen mode

The primary purpose of this pattern is to update props of sibling components. It makes the components more reusable and helps us to implement the "separation of concerns" more easily.

Let's take the following scenario as an example:

  • We need to develop a Form component.
  • Inside the From we have p and input.
  • The input is the input for the user.
  • The p shows what the user writes.

We can simply create something like this:

import React, { useState } from "react";
export default function Input(props) {
  return (
    <>
      <input
        type="text"
        value={props.value}
        onChange={props.onChange}
      />
    </>
  );
}

export default function Form() {
  const [value, setValue] = useState("");
  return (
    <form>
      <Input onChange={e => setValue(e.target.value)}/>
      <p>{value}</p>
    </form>
  );
}
Enter fullscreen mode Exit fullscreen mode

There are two issues with this approach:

1. We don't use the "septate of concern" concept in this case because the Input should control the Value and not the Form.

2. Our components are not so reusable and flexible.

We can refactor the code and use Render Props like this:

import React, { useState } from "react";

function Input(props) {
  const [value, setValue] = useState("");
  return (
    <>
      <input
        type="text"
        value={value}
        onChange={(e) => setValue(e.target.value)}
      />
      {props.render && props.render(value)}
    </>
  );
}

export default function Form() {
  return (
    <form>
      <Input render={(value) => <p>{value}</p>} />
    </form>
  );
}
Enter fullscreen mode Exit fullscreen mode

In this way the Input component controls the value, and it is much more reusable (the same functionality can be implemented with different elements).

HOC - Higher-Order Components

Higher-Order Components are basically a function that receive a component as an argument and return a new component with specific business logic inside. You maybe saw this in 'Redux':

export default connect(mapStateToProps , mapDispatchToProps)(From);
Enter fullscreen mode Exit fullscreen mode

With Higher-Order Components, you can write a separate functionality to your app's commons (global) functions and reuse it on diffident components in your project.

Let's take another scenario:

  • We need to develop two menu components.
  • Inside the first component, we have a button that needs to block the menu click event.
  • The second component is also a button, but this time we need to work with the menu click event.

The problem is that we need two kinds of menus - one with stopPropagation ability and the second without it.

We can use Higher-Order Components like this:

import React from "react";
import "./style.css";

function stopPropagation(WrappedComponent) {
  return function(){
    const handleClick = event => {
      event.stopPropagation();
      WrappedComponent.handleClick()
    };
     return <WrappedComponent onClick={handleClick} />;
  }
}

function Button(props){
  const handleClick = () => console.log("button clicked!");
  Button.handleClick = handleClick; 
  return <button onClick={props.onClick || handleClick}>Click Me</button>;
}

function Menu(props) {
  const openMenu = () => console.log("menu opened!");
  return (
    <div onClick={openMenu} className="menu">
      <h1>Menu</h1>
      {props.children}
    </div>
  );
}

export default function App() {
  const ButtonPropagation = stopPropagation(Button);
  return (
    <div>
      <Menu>
        <ButtonPropagation />
      </Menu>
      <Menu>
        <Button />
      </Menu>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Link to the demo

Let's analyze this code:

  • The App component reads the two Menus we mentioned.
  • The Menu component reads the title and the children (in this case, the Button).
  • Button has a button element with a click event. **handleClick is the basic functionality of the event.** We need to export this function using Button.handleClick= handleClick (in the class component you can do it with  static).
  • The stopPropagation is the Higher-Order Component. It receives a component (Button in our case) and sends back the component with new ability (in our case stopPropagation).

This is a simple example of the use of Higher-Order Components. We can use stopPropagation and don't need to rewrite again on different components. Even more importantly, we can create other "button" HOCs like preventDefault and queueClick.

Ok, that's all for part one of the article. In the second part, I will discuss the Context pattern, thePresentational and Container Components pattern and the compound components pattern.

Thank you for reading. I hope you enjoyed the tutorial and learned something new. If you have something to add, please leave a comment. And if you would like more information, here are some excellent resources on the subject:

Top comments (0)