DEV Community

Cover image for Transforming Complexity into Simplicity with useReducer: A Real-Time Chat Application
Gleidson Leite da Silva
Gleidson Leite da Silva

Posted on

Transforming Complexity into Simplicity with useReducer: A Real-Time Chat Application

Managing state in React applications can quickly become overwhelming, especially when dealing with complex scenarios such as real-time chat applications. While useState works well for simple state management, it can become cumbersome when handling multiple interrelated state variables. Enter useReducer, a powerful hook that can transform how you manage state in your React applications. This guide will delve into the advantages of useReducer and provide a detailed tutorial on implementing it in a real-time chat application.

Why Choose useReducer?

useReducer is particularly beneficial for managing complex state logic that involves multiple sub-variables. While useState is excellent for straightforward state management, useReducer provides a more organized and predictable approach, making it ideal for applications that require intricate state handling.

Key Benefits of useReducer

  1. Organization: Centralizes state logic in a single reducer function, enhancing code readability and maintainability.
  2. Predictability: Makes state transitions explicit, ensuring predictable application behavior.
  3. Ease of Testing: Facilitates unit testing of state logic, improving code reliability.
  4. Scalability: Enhances the scalability of applications by managing complex states in an organized manner.

Implementing useReducer in a Real-Time Chat Application

Let's explore a practical example: building a real-time chat application. This example will guide you through managing messages, online users, and connection status using useReducer.

Step 1: Setting Up the Environment

To follow this tutorial, set up a React environment. If you don't have one, you can create a new project using Create React App:

npx create-react-app chat-app
cd chat-app
npm start
Enter fullscreen mode Exit fullscreen mode

Step 2: Installing Dependencies

While React includes useReducer by default, we need a WebSocket library for real-time communication. We'll use ws.

npm install ws
Enter fullscreen mode Exit fullscreen mode

Step 3: Defining the Initial State and Reducer

First, define the initial state and reducer function. This centralizes state logic, making the code easier to understand and maintain.

import React, { useReducer, useEffect } from 'react';

// Define the initial state of the chat application
const initialState = {
  messages: [], // To store chat messages
  usersOnline: [], // To track online users
  connectionStatus: 'disconnected' // To monitor the WebSocket connection status
};

// Reducer function to handle state transitions based on dispatched actions
function chatReducer(state, action) {
  switch (action.type) {
    case 'ADD_MESSAGE':
      // Adds a new message to the messages array
      return {
        ...state,
        messages: [...state.messages, action.payload]
      };
    case 'USER_ONLINE':
      // Adds a new user to the usersOnline array
      return {
        ...state,
        usersOnline: [...state.usersOnline, action.payload]
      };
    case 'USER_OFFLINE':
      // Removes a user from the usersOnline array
      return {
        ...state,
        usersOnline: state.usersOnline.filter(user => user !== action.payload)
      };
    case 'SET_CONNECTION_STATUS':
      // Updates the connection status
      return {
        ...state,
        connectionStatus: action.payload
      };
    default:
      return state; // Returns the current state for any unknown action
  }
}
Enter fullscreen mode Exit fullscreen mode

Step 4: Using useReducer in the Component

Next, use useReducer in your chat component. This allows you to dispatch actions that modify the state predictably and efficiently.

function ChatApp() {
  // Initialize the useReducer hook with chatReducer and initialState
  const [state, dispatch] = useReducer(chatReducer, initialState);

  // useEffect hook to set up WebSocket connection and handle events
  useEffect(() => {
    // Create a new WebSocket connection
    const socket = new WebSocket('ws://chat-server.com');

    // Event handler for when the WebSocket connection is opened
    socket.onopen = () => {
      // Dispatch an action to update the connection status to 'connected'
      dispatch({ type: 'SET_CONNECTION_STATUS', payload: 'connected' });
    };

    // Event handler for receiving messages from the WebSocket server
    socket.onmessage = (event) => {
      const message = JSON.parse(event.data);
      // Dispatch an action to add the new message to the state
      dispatch({ type: 'ADD_MESSAGE', payload: message });
    };

    // Event handler for when the WebSocket connection is closed
    socket.onclose = () => {
      // Dispatch an action to update the connection status to 'disconnected'
      dispatch({ type: 'SET_CONNECTION_STATUS', payload: 'disconnected' });
    };

    // Clean up the WebSocket connection when the component unmounts
    return () => {
      socket.close();
    };
  }, []); // Empty dependency array to ensure this effect runs only once

  return (
    <div>
      <h1>Real-Time Chat</h1>
      <div>
        <h2>Connection Status: {state.connectionStatus}</h2>
        <ul>
          {state.messages.map((msg, index) => (
            <li key={index}>{msg}</li>
          ))}
        </ul>
      </div>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Step 5: Managing Online Users

Now, let's add logic to manage online users, making our chat application more robust.

useEffect(() => {
  // Create a new WebSocket connection
  const socket = new WebSocket('ws://chat-server.com');

  socket.onopen = () => {
    // Dispatch an action to update the connection status to 'connected'
    dispatch({ type: 'SET_CONNECTION_STATUS', payload: 'connected' });
  };

  socket.onmessage = (event) => {
    const data = JSON.parse(event.data);
    // Handle different types of messages from the server
    if (data.type === 'message') {
      // Dispatch an action to add the new message to the state
      dispatch({ type: 'ADD_MESSAGE', payload: data.payload });
    } else if (data.type === 'user_online') {
      // Dispatch an action to add a user to the online users list
      dispatch({ type: 'USER_ONLINE', payload: data.payload });
    } else if (data.type === 'user_offline') {
      // Dispatch an action to remove a user from the online users list
      dispatch({ type: 'USER_OFFLINE', payload: data.payload });
    }
  };

  socket.onclose = () => {
    // Dispatch an action to update the connection status to 'disconnected'
    dispatch({ type: 'SET_CONNECTION_STATUS', payload: 'disconnected' });
  };

  // Clean up the WebSocket connection when the component unmounts
  return () => {
    socket.close();
  };
}, []); // Empty dependency array to ensure this effect runs only once
Enter fullscreen mode Exit fullscreen mode

Step 6: Running and Testing the Application

Run the application with the command npm start and observe how useReducer manages the state effectively. Connect to the WebSocket server and watch as messages and online users are updated in real-time.

Conclusion: The Power of useReducer

The beauty of useReducer lies in its ability to transform complexity into simplicity. By centralizing state logic in a reducer function, we create an application that is more predictable, easier to maintain, and scalable.

useReducer is an essential tool for any React developer's toolkit. In complex applications like a real-time chat, it offers a clear and organized way to handle multiple states and their transitions, making the code easier to understand, maintain, and scale.

By adopting useReducer in your applications, you not only improve code clarity but also prepare your application to grow and adapt to future needs. So, the next time you face a complex state management challenge, remember useReducer and see how it can transform complexity into simplicity.

For more details on the ws library, you can check the official documentation on GitHub.

Top comments (0)