React Hook useCallback not updating State value

Viewed 24855

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.

2 Answers

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]);

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]
  );
Related