5

I've been following a guide to pass down a reducer to child components with useContext, but when dispatching from a child component, it did not seem to re-render the parent component.

The state itself seems to be correctly updating when dispatching from the Child component (as done by console.logging state.count), but the parent component does not reflect the change in state.

I've tried passing down a dispatch function as a prop to Child, and that did update App.js, but I was thinking that my approach was cleaner and I could be doing something wrong instead. I've also put {state.count} in Child.js and it did get updated properly, but for this situation I need it in the parent component. Here is the code:

App.js

import React, { useReducer } from 'react';
import { StateProvider } from './reducer';
import Child from './Child'

function App() {
    const reducer = (state, action) => {
        switch (action.type) {
            case 'add':
                console.log(state.count+1)
                return {...state, count: state.count + 1}
            default:
                throw new Error();
        }
    }

    const initialState = {count: 1};
    const [state, dispatch] = useReducer(reducer, initialState)

    return (
        <StateProvider initialState={initialState} reducer={reducer}>
            <div className="App">
                {state.count}
                <Child></Child>
            </div>
        </StateProvider>
  );
}

export default App;

reducer.js

import React, {createContext, useContext, useReducer} from 'react';

export const StateContext = createContext(null);

export const StateProvider = ({reducer, initialState, children}) => (
    <StateContext.Provider value={useReducer(reducer, initialState)}>
        {children}
    </StateContext.Provider>
);

export const useStateValue = () => useContext(StateContext);

Child.js

import React from 'react';
import { useStateValue } from './reducer';

export default function Child(props) {
    const [state, dispatch] = useStateValue();

    return (
        <button onClick={() => dispatch({type: 'add'})}>Increment</button>
    )
}
Sagiv b.g
  • 30,379
  • 9
  • 68
  • 99
Steve Hemmingsen
  • 383
  • 5
  • 19

1 Answers1

4

That's because you are re-creating a reducer in StateProvider:

<StateContext.Provider value={useReducer(reducer, initialState)}>

Instead, just pass the actual reducer's dispatch down:

<StateProvider initialState={initialState} reducer={{dispatch}}> 

And then use it:

const StateProvider = ({reducer, initialState, children}) => (
    <StateContext.Provider value={reducer}>
        {children}
    </StateContext.Provider>
);

BTW, i think you could just declare the reducer logic function outside your component so it won't get re-declared on each render of App for no reason.

Here is a running example:

// mimic imports
const {createContext, useContext, useReducer} = React;

const StateContext = createContext(null);

const StateProvider = ({reducer, initialState, children}) => (
    <StateContext.Provider value={reducer}>
        {children}
    </StateContext.Provider>
);

const useStateValue = () => useContext(StateContext);

function Child(props) {
  const {dispatch} = useStateValue(StateContext);

  return (
      <button onClick={() => dispatch({type: 'add'})}>Increment</button>
  )
}

const reducer = (state, action) => {
  switch (action.type) {
      case 'add':
          console.log(state.count+1)
          return {...state, count: state.count + 1}
      default:
          throw new Error();
  }
}

function App() {
    const initialState = {count: 1};
    const [state, dispatch] = useReducer(reducer, initialState)
    return (
        <StateProvider initialState={initialState} reducer={{dispatch}}>
            <div className="App">
                {state.count}
                <Child></Child>
            </div>
        </StateProvider>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.1/umd/react-dom.production.min.js"></script>
<div id="root"/>
Sagiv b.g
  • 30,379
  • 9
  • 68
  • 99