Rubypic

Ruby Jane Cabagnot

React Design Patterns

React Design Patterns are reusable solutions to common problems in React applications. They are a set of best practices that help developers write clean, maintainable, and scalable code. In this article, we will discuss some of the most common React Design Patterns and how to use them in your applications.

1. Container/Presenter Pattern (or Smart/Dumb Components)

The Container/Presenter Pattern is a design pattern that separates the logic and presentation of a component. The Container component is responsible for fetching data and managing state, while the Presenter component is responsible for rendering the UI. This pattern helps to keep the codebase clean and maintainable by separating concerns.

This pattern involves separating components into two categories: container components (smart components) and presentational components (dumb components). Container components manage the state and logic, while presentational components focus on how things look.

Analogy: Think of the container as the brain, controlling and managing the state and logic, while the presentational component is like a puppet, only concerned with how it presents itself.

// Container Component
const Container = () => {
  const [data, setData] = useState([]);

  useEffect(() => {
    fetchData();
  }, []);

  const fetchData = async () => {
    const response = await fetch("https://api.example.com/data");
    const data = await response.json();
    setData(data);
  };

  return <Presenter data={data} />;
};

// Presenter Component

const Presenter = ({ data }) => {
  return (
    <div>
      {data.map((item) => (
        <div key={item.id}>{item.name}</div>
      ))}
    </div>
  );
};

In this example, the Container component fetches data from an API and passes it to the Presenter component as a prop. The Presenter component is responsible for rendering the UI based on the data it receives.

2. Higher Order Component (HOC) Pattern

The Higher Order Component (HOC) Pattern is a design pattern that allows you to reuse logic across multiple components. It is a function that takes a component as an argument and returns a new component with additional functionality. HOCs are commonly used for tasks such as data fetching, authentication, and code splitting.

Analogy: It’s like adding a power-up to a video game character that enhances their abilities.

const withData = (Component) => {
  return (props) => {
    const [data, setData] = useState([]);

    useEffect(() => {
      fetchData();
    }, []);

    const fetchData = async () => {
      const response = await fetch("https://api.example.com/data");
      const data = await response.json();
      setData(data);
    };

    return <Component data={data} {...props} />;
  };
};

const MyComponent = ({ data }) => {
  return (
    <div>
      {data.map((item) => (
        <div key={item.id}>{item.name}</div>
      ))}
    </div>
  );
};

const MyComponentWithData = withData(MyComponent);

In this example, the withData function is a Higher Order Component that fetches data from an API and passes it to the wrapped component as a prop. The MyComponent component is wrapped with the withData HOC to add data fetching functionality.

3. Render Props Pattern

The Render Props Pattern is a design pattern that allows you to share code between components using a prop whose value is a function. This pattern is commonly used for sharing state, logic, or UI between components.

const DataProvider = ({ children }) => {
  const [data, setData] = useState([]);

  useEffect(() => {
    fetchData();
  }, []);

  const fetchData = async () => {
    const response = await fetch("https://api.example.com/data");
    const data = await response.json();
    setData(data);
  };

  return children(data);
};

const MyComponent = () => {
  return (
    <DataProvider>
      {(data) => (
        <div>
          {data.map((item) => (
            <div key={item.id}>{item.name}</div>
          ))}
        </div>
      )}
    </DataProvider>
  );
};

In this example, the DataProvider component fetches data from an API and passes it to its children as a function prop. The MyComponent component renders the UI based on the data it receives from the DataProvider component.

4. Context API Pattern

The Context API Pattern is a design pattern that allows you to share state between components without having to pass props down through the component tree. It provides a way to pass data through the component tree without having to pass props down manually at every level.

const DataContext = createContext();

const DataProvider = ({ children }) => {
  const [data, setData] = useState([]);

  useEffect(() => {
    fetchData();
  }, []);

  const fetchData = async () => {
    const response = await fetch("https://api.example.com/data");
    const data = await response.json();
    setData(data);
  };

  return <DataContext.Provider value={data}>{children}</DataContext.Provider>;
};

const MyComponent = () => {
  const data = useContext(DataContext);

  return (
    <div>
      {data.map((item) => (
        <div key={item.id}>{item.name}</div>
      ))}
    </div>
  );
};

In this example, the DataContext is created using the createContext function. The DataProvider component fetches data from an API and provides it to its children using the DataContext.Provider component. The MyComponent component uses the useContext hook to access the data provided by the DataProvider component.

5. Compound Components Pattern

The Compound Components Pattern is a design pattern that allows you to create components that work together to achieve a common goal. It is a way to group related components together and manage their state and behavior collectively.

const Tabs = ({ children }) => {
  const [activeTab, setActiveTab] = useState(0);

  return (
    <div>
      <div>
        {children.map((child, index) => (
          <button key={index} onClick={() => setActiveTab(index)}>
            {child.props.label}
          </button>
        ))}
      </div>
      <div>{children[activeTab]}</div>
    </div>
  );
};

const Tab = ({ children }) => {
  return <div>{children}</div>;
};

const App = () => {
  return (
    <Tabs>
      <Tab label="Tab 1">Content for Tab 1</Tab>
      <Tab label="Tab 2">Content for Tab 2</Tab>
      <Tab label="Tab 3">Content for Tab 3</Tab>
    </Tabs>
  );
};

In this example, the Tabs component manages the state of the active tab and renders the tab buttons and content based on the active tab. The Tab component represents a single tab and its content. The App component uses the Tabs and Tab components to create a tabbed interface.

6. Controlled Components Pattern

The Controlled Components Pattern is a design pattern that allows you to manage the state of form elements in React. It involves storing the state of form elements in the component’s state and updating it in response to user input.

const MyForm = () => {
  const [name, setName] = useState("");
  const [email, setEmail] = useState("");

  const handleNameChange = (event) => {
    setName(event.target.value);
  };

  const handleEmailChange = (event) => {
    setEmail(event.target.value);
  };

  const handleSubmit = (event) => {
    event.preventDefault();
    console.log(name, email);
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="text"
        value={name}
        onChange={handleNameChange}
        placeholder="Name"
      />
      <input
        type="email"
        value={email}
        onChange={handleEmailChange}
        placeholder="Email"
      />
      <button type="submit">Submit</button>
    </form>
  );
};

In this example, the MyForm component manages the state of the name and email form fields using the useState hook. The handleNameChange and handleEmailChange functions update the state in response to user input. The form is submitted when the user clicks the submit button, and the form data is logged to the console.

Conclusion

React Design Patterns are a powerful tool for building scalable and maintainable React applications. By following best practices and using common patterns, you can write clean, reusable code that is easy to understand and maintain. I hope this article has given you a good overview of some of the most common React Design Patterns and how to use them in your applications. Happy coding!