skip to Main Content

I have a big component, inside I have 4 subcomponents, each 4 subcomponents have a couple of components each. Only two levels of components, but I still pass a lot of props and handlers and I have all the state in the top component:

const MyBigComp = () => {
  const [a, setA] = useState(false);
  const [b, setB] = useState([]);
  const [c, setC] = useState(0);
  const [d, setD] = useState(true);
  const [e, setE] = useState({ f: [], g: [] });
  // I have ~20 more useState hooks in here.

  // Then I have a lot of handlers:

  const toggleX = () => {
    setA(prev => !prev);
  }

  const addY = () => {
    setB(prev => ([...prev, { z: 1 }]));
  }

  // And I have more useEffects and other handlers in here also.

  return (
    <Comp1 a={a} toggleX={toggleX} /> // For simplicity I only pass two props but in reality there are like 10-12 props for each component.
    <Comp2 b={b} addY={addY} />
    <Comp3 c={c} toggleX={toggleX} />
    <Comp4 d={d} e={e} addY={addY} />
  );
}

My first thought would be to lower the state whenever possible, but every piece of state needs to be in the parent component because it’s used by a handler or shared to other components.

I thought of using Context but I don’t know if it’s the best solution here since prop drilling is not much of an issue since it’s only two levels down.

2

Answers


  1. This sounds like a case for useReducer, which is an ergonomic way of dealing with complex state management in a single state object.

    Your code looks like it could be modeled roughly like this:

    function reducer(state, action) {
      switch (action.type) {
        case 'toggle': {
          return {
            ...state,
            [action.field]: !state[action.field]
          };
        }
        case 'set': {
          return {
            ...state,
            [action.field]: action.value
          };
        }}
        case 'add': {
          return {
            ...state,
            [action.field]: [...state[action.field], action.value]
          };
        }
      }
      throw Error('Unknown action: ' + action.type);
    }
    

    Which you can then use like this:

      const [state, dispatch] = useReducer(reducer, { initialStateHere });
      
      function handleToggle() {
        dispatch({ type: 'toggle', field: "abc" });
      }
    
      function handleInputChange(e) {
        dispatch({
          type: 'add',
          field: 'myList',
          value: "my new todo"
        });
      }
    
    Login or Signup to reply.
  2. It’s a bit difficult to say what is the best option to you case because the context that you gave is too short, but I think you just need to use react’s ContextApi to reduce the amout of the useStates and avoid prop drilling.

    StoreContext.js:

    import { createContext } from "react";
    export const StoreContext = createContext(null);
    

    App.jsx:

    import { useState } from "react";
    import { StoreContext } from "./StoreContext";
    import MyBigComp from "./MyBigComp";
    
    export default function App() {
      const [store, setStore] = useState({
        a: false,
        b: [],
        c: 0,
        d: true,
        e: { f: [], g: [] }
        // You can have ~20 more in here...
      });
    
      return (
        <StoreContext.Provider value={{ store, setStore }}>
          <MyBigComp />
        </StoreContext.Provider>
      );
    }
    

    MyBigComp.jsx:

    import { useContext } from "react";
    import { StoreContext } from "./StoreContext";
    
    export default function MyBigComp() {
      const { store, setStore } = useContext(StoreContext);
    
      const toggleX = () => {
        const newAState = !store.a;
        setStore({ ...store, a: newAState });
      };
    
      const addY = () => {
        const newBState = [...store.b, { z: 1 }];
        setStore({ ...store, b: newBState });
      };
    
      return (
        <>
          <Comp1 a={store.a} toggleX={toggleX} /> // For simplicity I only pass two
          props but in reality there are like 10-12 props for each component.
          <Comp2 b={store.b} addY={addY} />
          <Comp3 c={store.c} toggleX={toggleX} />
          <Comp4 d={store.d} e={store.e} addY={addY} />
        </>
      );
    }
    

    I think that this is what you need for now, but of course, if you need to improve you code, you can use useReducer hook, for example, or even redux/zustand/jotai or other state manager library.

    I hope I had help

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search