React useContext not triggering re-rendering

Viewed 388

I have a button in a child component AddMore.js which adds more items on a list, but it's not re-rendering the other child component that renders that list Pig.js. I thought my setup would work because I set up the context and provider in a similar fashion using the NextJS framework. Here's the source code, using codesandbox, or you can look at the code below.

App.js

import React, { useState } from "react";

import "./App.css";

import PigContext from "./store/PigContext";

import Pig from "./Pig";
import AddMore from "./AddMore";

function App() {
  const [data, setData] = useState([
    { pig: "oink" },
    { pig: "bork" },
    { pig: "oinkoink" }
  ]);

  return (
    <div className="App">
      <header className="App-header">
        <PigContext.Provider value={{ data, setData }}>
          <Pig />
          <AddMore />
        </PigContext.Provider>
      </header>
    </div>
  );
}

export default App;

PigContext.js

import { createContext } from "react";

const PigContext = createContext(undefined);

export default PigContext;

Pig.js

import React, { useContext } from "react";

import PigContext from "./store/PigContext";

const Pig = () => {
  const { data } = useContext(PigContext);

  return (
    <div>
      {data.map(({ pig }, idx) => (
        <div key={idx}>{pig}</div>
      ))}
    </div>
  );
};

export default Pig;

AddMore.js

import React, { useContext } from "react";
import PigContext from "./store/PigContext";

export default function AddMore() {
  const { setData } = useContext(PigContext);

  return (
    <div>
      <button
        onClick={() =>
          setData(prev => {
            prev.push({ pig: "zoink" });
            console.log(prev);
            return prev;
          })
        }
      >
        add pig question
      </button>
    </div>
  );
}
2 Answers

You are mutating the state object by calling push, you should return a new array instead in order to get the component updated.

AddMore.js:

...
  <button
    onClick={() =>
      setData(prev => (
        [...prev, ({ pig: "zoink" })]
      ))
    }
  >
...

It is because you are a mutating state in setData using push. using a method that doesn't mutate state like concat can fix the issue. Your AddMore function will look like

function AddMore() {

 const { setData } = useContext(PigContext);

  return (
    <div>
      <button onClick={() => setData(prev => prev.concat({ pig: "zoink" }))}>
        add pig question
      </button>
    </div>
  );
}
Related