React Hook useCallback not updating State value
Asked Answered
P

3

13

I'm new to React Hooks. I have a UI with multiple inputs with values in an object. I create the UI with a loop, so I would like to have a single callback for the updating the inputs.

enter image description here

The "Shop" input uses it's own callback and successfully updates the state. But the "Product" inputs never update the state. I don't really understand why these two callbacks behave differently.

The code is below. The problem is in the handleChange callback.

import React, { useCallback, useState, useEffect } from "react";
import { TextField, Form } from "@shopify/polaris";

export default function TextFieldExample() {
  const [values, setValues] = useState({
    "prod-0": "Jaded Pixel",
    "prod-1": "blue diamonds"
  });
  const [shop, setShop] = useState("Joe's house of pancakes");

  const handleChangeShop = useCallback(newName => {
    setShop(newName);
  }, []);

  const handleChange = useCallback((newValue, id) => {
      console.log("Pre: values:", values);
      console.log(id, newValue);
      const newProds = values;
      newProds[id] = newValue;
      setValues(newProds);
      console.log("Post: newProds:", newProds);
    }, [values]);

  useEffect(() => {    // observing if State changes
    console.log("in useEffect: shop:", shop); // this gets called and is updated when changed.
    console.log("in useEffect: values:", values); // this never gets called or updated.
  }, [values, shop]);

  const items = [];
  Object.keys(values).forEach(function(prod) {
    items.push(
      <TextField label={"Product " + prod.substr(5)} id={prod} value={values[prod]} onChange={handleChange} />
    );
  });
  return (
    <Form>
      <TextField label="Shop" id="shop" value={shop} onChange={handleChangeShop}/>
      {items}
    </Form>
  );
}

Code Sandbox is here: https://codesandbox.io/s/fast-tdd-1ip38
Try it out and look at the console.

Penurious answered 29/3, 2020 at 7:9 Comment(1)
You are mutating state values object instead of returning a new object reference.Dumfound
K
18

You are mutating the values state, see this sandbox

Change your handleChange function to

const handleChange = useCallback((newValue, id) => {
  const newProds = { ...values };
  newProds[id] = newValue;
  setValues(newProds);
}, [values]);

You can change it further to

const handleChange = useCallback((newValue, id) => {
  setValues(prods => ({...prods, [id] : newValue }));
}, [values]);
Kerch answered 29/3, 2020 at 7:17 Comment(1)
I'm such an idiot! Thank you! I was so focused on the "React-ness" and missed the obvious JS mess-up!Penurious
M
5

Try updating handleChange to this:

const handleChange = useCallback(
    (newValue, id) => {
      console.log("Pre: values:", values);
      console.log(id, newValue);
      setValues(state => ({ ...state, [id]: newValue}));
    },
    [values]
  );
Merridie answered 29/3, 2020 at 7:19 Comment(1)
You do not need the useCallback in this situationDisadvantaged
D
0

An even more concise solution:

const handleChange = (newValue, id) => {
  setValues(prevState => ({...prevState, [id] : newValue}));
};

React state variables should never be modified directly.

Disadvantaged answered 28/9, 2023 at 22:25 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.