React Component Design Patterns - Part 2

Fatemeh Paghar - Mar 26 - - Dev Community

5. State reducer pattern

The State Reducer Pattern in React is a design pattern used to manage state in a more controlled and predictable manner. It involves using a reducer function to handle state transitions and actions, similar to how reducers work in Redux. This pattern can be particularly useful for managing complex state logic or state that needs to be shared across multiple components.

Let's walk through an example of implementing the State Reducer Pattern in React for managing a a social media dashboard application.In this example, we'll manage the state of the user's posts and notifications using a reducer function.

import React, { useReducer } from 'react';

// Action types
const ADD_POST = 'ADD_POST';
const DELETE_POST = 'DELETE_POST';
const ADD_NOTIFICATION = 'ADD_NOTIFICATION';
const DELETE_NOTIFICATION = 'DELETE_NOTIFICATION';

// Reducer function
const dashboardReducer = (state, action) => {
  switch (action.type) {
    case ADD_POST:
      return { ...state, posts: [...state.posts, action.payload] };
    case DELETE_POST:
      return { ...state, posts: state.posts.filter(post => post.id !== action.payload.id) };
    case ADD_NOTIFICATION:
      return { ...state, notifications: [...state.notifications, action.payload] };
    case DELETE_NOTIFICATION:
      return { ...state, notifications: state.notifications.filter(notification => notification.id !== action.payload.id) };
    default:
      return state;
  }
};

// Dashboard component
const Dashboard = () => {
  // Initialize state using useReducer hook
  const [state, dispatch] = useReducer(dashboardReducer, { posts: [], notifications: [] });

  // Add post function
  const addPost = (text) => {
    const newPost = { id: Date.now(), text };
    dispatch({ type: ADD_POST, payload: newPost });
  };

  // Delete post function
  const deletePost = (id) => {
    dispatch({ type: DELETE_POST, payload: { id } });
  };

  // Add notification function
  const addNotification = (text) => {
    const newNotification = { id: Date.now(), text };
    dispatch({ type: ADD_NOTIFICATION, payload: newNotification });
  };

  // Delete notification function
  const deleteNotification = (id) => {
    dispatch({ type: DELETE_NOTIFICATION, payload: { id } });
  };

  return (
    <div>
      <h1>Social Media Dashboard</h1>
      <div>
        <h2>Posts</h2>
        <ul>
          {state.posts.map(post => (
            <li key={post.id}>
              {post.text}
              <button onClick={() => deletePost(post.id)}>Delete</button>
            </li>
          ))}
        </ul>
        <button onClick={() => addPost('New post')}>Add Post</button>
      </div>
      <div>
        <h2>Notifications</h2>
        <ul>
          {state.notifications.map(notification => (
            <li key={notification.id}>
              {notification.text}
              <button onClick={() => deleteNotification(notification.id)}>Dismiss</button>
            </li>
          ))}
        </ul>
        <button onClick={() => addNotification('New notification')}>Add Notification</button>
      </div>
    </div>
  );
};

export default Dashboard;

Enter fullscreen mode Exit fullscreen mode

In this example:

  • We define action types (ADD_POST, DELETE_POST, ADD_NOTIFICATION, DELETE_NOTIFICATION) that represent different actions that can be performed on the social media dashboard state.
  • We define a reducer function (dashboardReducer) that takes the current state and an action and returns the new state based on the action.
  • We use the useReducer hook to create a stateful value (state) and a dispatch function to update the state by passing actions to the reducer.
  • The Dashboard component contains logic for adding and deleting posts and notifications. It dispatches actions to the reducer to update the state accordingly.
  • In the UI, posts, and notifications are rendered as list items with buttons to delete them.
  • Users can add new posts or notifications by clicking on the respective buttons.

This example demonstrates how to implement the State Reducer Pattern in React to manage the state of a social media dashboard, including posts and notifications. The reducer function centralizes state management logic, making it easier to understand and maintain.

6- Component Composition pattern

Component Composition pattern involves composing smaller, reusable components together to create more complex components or UIs. Let's illustrate this pattern with a social media dashboard example.

Suppose we have a social media dashboard that consists of several components such as UserInfo, Feed, Notifications, and Sidebar. We can compose these smaller components together to create the social media dashboard component.

import React from 'react';

// UserInfo component
const UserInfo = ({ user }) => {
  return (
    <div className="user-info">
      <img src={user.profilePic} alt="Profile" />
      <h3>{user.name}</h3>
    </div>
  );
};

// Feed component
const Feed = ({ posts }) => {
  return (
    <div className="feed">
      <h2>Feed</h2>
      <ul>
        {posts.map((post, index) => (
          <li key={index}>
            <h4>{post.title}</h4>
            <p>{post.content}</p>
          </li>
        ))}
      </ul>
    </div>
  );
};

// Notifications component
const Notifications = ({ notifications }) => {
  return (
    <div className="notifications">
      <h2>Notifications</h2>
      <ul>
        {notifications.map((notification, index) => (
          <li key={index}>
            <p>{notification}</p>
          </li>
        ))}
      </ul>
    </div>
  );
};

// Sidebar component
const Sidebar = () => {
  return (
    <div className="sidebar">
      <ul>
        <li>Home</li>
        <li>Profile</li>
        <li>Messages</li>
        <li>Settings</li>
      </ul>
    </div>
  );
};

// SocialMediaDashboard component composed of smaller components
const SocialMediaDashboard = ({ user, posts, notifications }) => {
  return (
    <div className="social-media-dashboard">
      <UserInfo user={user} />
      <div className="main-content">
        <Feed posts={posts} />
        <Notifications notifications={notifications} />
      </div>
      <Sidebar />
    </div>
  );
};

// Main component
const App = () => {
  // Sample data
  const user = {
    name: "John Doe",
    profilePic: "https://via.placeholder.com/150",
  };

  const posts = [
    { title: "Post 1", content: "Content of post 1" },
    { title: "Post 2", content: "Content of post 2" },
    { title: "Post 3", content: "Content of post 3" },
  ];

  const notifications = [
    "You have a new friend request",
    "Your post has been liked",
    "You have a new message",
  ];

  return (
    <div>
      <h1>Social Media Dashboard</h1>
      <SocialMediaDashboard user={user} posts={posts} notifications={notifications} />
    </div>
  );
};

export default App;

Enter fullscreen mode Exit fullscreen mode

In this example:

  • We have separate components for UserInfo, Feed, Notifications, and Sidebar, each responsible for rendering a specific part of the social media dashboard.
  • We compose these smaller components together in the SocialMediaDashboard component to create the entire social media dashboard UI.
  • The App component serves as the entry point where we pass sample data (user information, posts, notifications) to the SocialMediaDashboard component.

This demonstrates how we can use the Component Composition pattern to build a social media dashboard by composing smaller, reusable components together. Each component focuses on a specific aspect of the UI, promoting reusability and maintainability.

References:

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .