DEV Community

Cover image for react 19 useContext, useReducer & localstorage
Muyiwa Johnson
Muyiwa Johnson

Posted on

react 19 useContext, useReducer & localstorage

Solving the State Management Problem in React: A Deep Dive into Centralized Notes Management

Introduction

As applications grow, managing state across various components becomes increasingly complex. Prop drilling can make your codebase cumbersome, and the lack of persistence between sessions can frustrate users who lose their data upon refreshing the browser. To tackle these issues, we'll explore a robust solution using React context, hooks, and reducers to manage and persist notes in a structured and maintainable way.

you might just need this if you are experienced

react context reducer & local storage

The Problem

  1. Prop Drilling: Passing state down multiple levels of components (prop drilling) leads to cluttered and hard-to-maintain code.
  2. State Persistence: Without persistent storage, data is lost when the user refreshes or closes the browser.
  3. State Initialization: Initializing state on every render can lead to performance issues.
  4. Global State Access: Components need a straightforward way to access and update global state.
  5. Error Handling: Reading from or writing to storage can lead to application crashes if not handled properly.
  6. Scalability: As the application grows, state management becomes harder to maintain and scale.

Our Solution: Centralized Notes Management

To address these problems, we’ll use a combination of React's createContext, useContext, useReducer, and useEffect hooks along with localStorage for persistence. Here’s a detailed breakdown of the approach:

1. Centralized State Management

We start by creating a context to centralize the state:

import { createContext, useContext, useReducer, useEffect } from "react";
import NoteReducer from "../reducers/NoteReducer";
import InitialNotes from "../state/InitialNotes";

const NoteContext = createContext(null);
const NoteDispatchContext = createContext(null);
Enter fullscreen mode Exit fullscreen mode

By using createContext, we ensure that our state and dispatch functions are accessible globally within our application.

2. Persistence with Local Storage

We use localStorage to ensure that notes data persists across sessions:

const getStoredNotes = (initialNotes = InitialNotes) => {
  try {
    const storedNotes = localStorage.getItem("storedNotes");
    return storedNotes ? JSON.parse(storedNotes) : initialNotes;
  } catch (error) {
    console.error("Error reading from localStorage:", error);
    return initialNotes;
  }
};
Enter fullscreen mode Exit fullscreen mode

This function retrieves notes from localStorage or falls back to initial notes if an error occurs, ensuring robustness.

3. Lazy State Initialization

By initializing the state lazily, we improve performance:

const NotesProvider = ({ children }) => {
  const [notes, dispatch] = useReducer(NoteReducer, undefined, getStoredNotes);

  useEffect(() => {
    try {
      localStorage.setItem("storedNotes", JSON.stringify(notes));
    } catch (error) {
      console.error("Error saving to localStorage:", error);
    }
  }, [notes]);

  return (
    <NoteContext.Provider value={notes}>
      <NoteDispatchContext.Provider value={dispatch}>
        {children}
      </NoteDispatchContext.Provider>
    </NoteContext.Provider>
  );
};
Enter fullscreen mode Exit fullscreen mode

The useReducer hook with lazy initialization ensures that state is only loaded from localStorage once when the component mounts.

4. Global Access and Modification

To simplify state access and updates, we create custom hooks:

export const useNotesContext = () => useContext(NoteContext);
export const useNotesDispatchContext = () => useContext(NoteDispatchContext);
Enter fullscreen mode Exit fullscreen mode

These hooks allow any component to easily access and modify the notes state.

5. Structured State Updates

Using a reducer centralizes the logic for state updates:

// NoteReducer.js
const NoteReducer = (state, action) => {
  switch (action.type) {
    case 'ADD_NOTE':
      return [...state, action.payload];
    case 'DELETE_NOTE':
      return state.filter(note => note.id !== action.payload.id);
    default:
      return state;
  }
};

export default NoteReducer;
Enter fullscreen mode Exit fullscreen mode

This pattern ensures that state management is predictable and easy to maintain.

6. Error Handling for Data Persistence

By wrapping localStorage operations in try-catch blocks, we prevent the application from crashing due to storage-related errors:

useEffect(() => {
  try {
    localStorage.setItem("storedNotes", JSON.stringify(notes));
  } catch (error) {
    console.error("Error saving to localStorage:", error);
  }
}, [notes]);
Enter fullscreen mode Exit fullscreen mode

7. Optimization for Re-renders

The useEffect hook with a dependency on the notes state ensures that localStorage is updated only when the notes state changes, minimizing unnecessary operations:

useEffect(() => {
  try {
    localStorage.setItem("storedNotes", JSON.stringify(notes));
  } catch (error) {
    console.error("Error saving to localStorage:", error);
  }
}, [notes]);
Enter fullscreen mode Exit fullscreen mode

8. Security Consideration

While localStorage is convenient, it's important to consider security for sensitive data. For more secure storage, consider using encrypted storage options or other secure methods.

9. Scalability and Maintenance

The context and reducer pattern makes the codebase scalable and maintainable. Adding new features or modifying existing logic becomes easier as the state management logic is centralized and well-structured.

Conclusion

By leveraging React context, hooks, and reducers, we can create a robust solution for managing and persisting notes. This approach addresses common problems like prop drilling, state persistence, and performance issues, while providing a scalable and maintainable architecture. Whether you're building a simple note-taking app or a more complex application, these principles will help you manage state effectively and improve the overall user experience.

Top comments (0)