skip to Main Content

I am trying to update the Child component if the reducer function in Parent component runs successfully.

function reducer(state, action) {
  if (action.type === "something") {
    try {
      action.cb();
      return { ...state, updated: true };
    } catch (error) {
      action.cb(error)
    }
  }
}

function Parent() {
  const [state, dispatch] = useReducer(reducer)

  return <Child dispatch={dispatch}></Child>
}
function Child({ dispatch }) {
  const [state, setState] = useState(false)

  function handleClick () {
    function cb(err) {
      if (!err) {
        setState(true)
      } 
    }
        
    dispatch({ type: "something", cb: cb });
  }

  return <button onClick={handleClick}>Click me</button>;
}

When I run the above code I get a warning:

Warning: Cannot update a component (Child) while rendering a different
component (Parent)

How can I update the Child component’s state if there is no error in the reducer function?

2

Answers


  1. Do Not Put Non-Serializable Values in State or Actions

    You can subscribe to the changes of the updated state in the child component and set its state.

    There is no need to use try/catch, if the updated state is not updated correctly, it still stays false value.

    import { useEffect, useReducer, useState } from 'react';
    
    function reducer(state, action) {
      if (action.type === 'something') {
        return { ...state, updated: true };
      }
    }
    
    function Parent() {
      const [state, dispatch] = useReducer(reducer, { updated: false });
    
      return <Child dispatch={dispatch} updated={state.updated}></Child>;
    }
    
    function Child({ dispatch, updated }) {
      const [state, setState] = useState(false);
    
      useEffect(() => {
        if (updated) {
          setState(true);
        }
      }, [updated]);
    
      function handleClick() {
        dispatch({ type: 'something' });
      }
    
      return (
        <div>
          <button onClick={handleClick}>Click me</button>
          <p>child state: {JSON.stringify(state)}</p>
        </div>
      );
    }
    
    function App() {
      return <Parent />;
    }
    
    export default App;
    

    stackblitz

    See also:

    Login or Signup to reply.
  2. Reducers are to be considered pure functions… don’t pass in functions and invoke them from within your reducer function. Issue the side-effects appropriately via the useEffect hook or in an event callback, e.g. like handleClick.

    In this case, issue the side-effect in the handleClick callback, and if that is successful, dispatch the action to update your state.

    function reducer(state, action) {
      if (action.type === "something") {
        return { ...state, updated: true };
      }
    }
    
    function Parent() {
      const [state, dispatch] = useReducer(reducer);
    
      return <Child dispatch={dispatch} />;
    }
    
    function Child({ dispatch }) {
      const [state, setState] = useState(false);
    
      async function handleClick () {
        try {
          /* some asynchronous logic or code that can reject or throw error */
    
          // success
          setState(true);
          dispatch({ type: "something" });
        } catch(error) {
          // failure
        }
      }
    
      return <button onClick={handleClick}>Click me</button>;
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search