1

Here is a CodeSandbox that has the example code below, and the linter highlights some of the issues: https://codesandbox.io/s/react-repl-bw2h1

Below is a basic example of what I'm trying to do. In a container component, I have a context AppContext that is providing state to child components, <ChildConsumer /> and <ChildDispatcher />.

The <ChildConsumer /> component is receiving this state using useContext, and this seems to be working as expected.

Inside <ChildDispatcher />, I'm trying to dispatch an action when a button is clicked. To do this, I've created a reducer reducer that handles an action. I've also set up useReducer here that takes in reducer and the initial store state.

When I click the button, nothing happens. What I'm expecting to happen is that the dispatch receives both the state pulled off of useReducer as well as an action object, and passes these to the reducer. The reducer should see that the action of type BUTTON_CLICKED was received, and should return a new state containing the old state as well as an additional 'goodbye' item. Then, the child component <ChildConsumer /> should rerender with this new state.

import React, { createContext, useContext, useReducer } from "react";
import ReactDOM from "react-dom";

const store = [""];
const AppContext = createContext(store);

const ChildDispatcher = () => {
  const reducer = (state, action) => {
    switch (action.type) {
      case "BUTTON_CLICKED":
        return [...state, "goodbye"];
      default:
        return state;
    }
  };

  const [state, dispatch] = useReducer(reducer, store);
  const handleClick = () =>
    dispatch(state, {
      type: "BUTTON_CLICKED"
    });
  return <button onClick={handleClick}>press me</button>;
};

const ChildConsumer = () => {
  const [consumer] = useContext(AppContext);
  return <div>{consumer}</div>;
};

const App = () => {
  return (
    <div>
      <h1>Using Context and useReducer</h1>
      <AppContext.Provider value={["hello"]}>
        <ChildConsumer />
        <ChildDispatcher />
      </AppContext.Provider>
    </div>
  );
};

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
rpivovar
  • 3,150
  • 13
  • 41
  • 79

2 Answers2

4

I have made a small amendment in you code.

you have to pass the dispatch like below. Dispatch expects an argument of type object.

const handleClick = () => dispatch({ type: "BUTTON_CLICKED" });

And then this state could be accessed like this.

const ChildDispatcher = () => {
  const reducer = (state, action) => {
    switch (action.type) {
      case "BUTTON_CLICKED":
        //action.state      // like this
        return [...state, "goodbye"];
      default:
        return state;
    }
  };

  const [state, dispatch] = useReducer(reducer, store);
  const handleClick = () =>
    dispatch(state, {
      type: "BUTTON_CLICKED"
    });
  return <button onClick={handleClick}>press me</button>;
};

By default react will pass the state to the dispatcher. but if you want to pass some data the you can add it in the object and pass that object to dispatch.

const handleClick = () => dispatch({ type: "BUTTON_CLICKED", state: state });

CodeSandBox:

Edit react repl

Sohail Ashraf
  • 10,078
  • 2
  • 26
  • 42
  • Thanks. I can see how this is updating in the `` component from the log you put in place. Is it possible for this state to also be pushed to the `` component? – rpivovar Mar 07 '20 at 17:38
  • Didn't know I was calling the dispatch function wrong. Thanks for the tip on that, too – rpivovar Mar 07 '20 at 17:41
  • yes, you can get the state in ChildConsumer as well, as it's also consuming the same `Context` – Sohail Ashraf Mar 07 '20 at 17:42
2

Few problems with this:

  1. The ChildDispatch state is only available to ChildDispatch and will not affect upper level components. To change the context value, you need to provide a dispatch in that component and make a custom hook (or pass it as props) to use it in the ChildDispatch.

  2. Don’t pass state when calling your dispatch. useReducer will handle that for you. Just send the action.

This is what it means that there should be a one way direction for your data flow. Parent components control the shared state / way to manage state, and child components use that to render / perform actions.

Asher Gunsay
  • 259
  • 1
  • 5
  • Thanks. I think I looked into setting up this reducer function in the parent and then passing the dispatch as a prop through context somehow, but was having trouble (and also wasn't sure if this was an anti-pattern, or something that I shouldn't be doing). I'll look more into that. – rpivovar Mar 07 '20 at 17:39
  • The reason I looked into passing it through context and not just as a prop is because in my actual project, the child components are nested down a bit rather than directly underneath the parent component. – rpivovar Mar 07 '20 at 17:42