Search code examples
reactjsreact-state

React updating state's object array property


I have an object in a React state:

{
    name: "",
    price: 0.0,
    components: [],
 }

and I am trying to modify its components property. I can see in the debugger that the state is updated but the rendering is not updated, so the different nested objects are not rendered.

I guess this is very similar to what is described here:

But I have the feeling I am doing what is precognised, using an updater function, but it doens't work.

Here is the code:

import React, { useState } from "react";

import "./styles.css";

defaultComponent = {
  name: "C1",
  comment: "",
}

export default function App() {

  const [system, setSystem] = useState({
    name: "",
    price: 0.0,
    components: [],
  })

  function modifySystemProperty(property, newValue) {
    let modifiedSystem = { ...system };
    modifiedSystem[property] = newValue;
    setSystem(modifiedSystem);
  }

  function addComponent() {
    setSystem((system) => ({
      ...system,
      components: [...system.components, defaultComponent],
    }));
};

  return (
    <div className="App">
      <h1>Hello CodeSandbox</h1>
      <h3>Edit system:</h3>
      <form>
        <div>
          <label htmlFor="form-name">Name: </label>
          <input
            id="form-name"
            type="text"
            value={system?.name}
            onChange={(e) => modifySystemProperty("name", e.target.value)}
          />
        </div>
        <div>
          <label htmlFor="form-price">Price: </label>
          <input
            id="form-price"
            type="number"
            min="0"
            step="0.1"
            value={system?.price}
            onChange={(e) => modifySystemProperty("price", +e.target.value)}
          />
        </div>
        <div>
          <p>Components:</p>
          <ul>
            {system.components.map((c, idx) => {
              <li key={idx}>Component: {c.name}</li>
            })}
          </ul>
          <button
            onClick={() => addComponent()}
          >
            + Add Component
          </button>
        </div>
      </form>
    </div>
  );
}

And running on CodeSandBox here: https://codesandbox.io/p/sandbox/thirsty-paper-jzsjfy Can you tell me the proper way of updating this object property? Thank you.


Solution

  • I see at least two issues:

    1. Your button is refreshing the page every time it is clicked. This is because a single button inside a form defaults to type="submit". You need to change it to <button type="button">.
    2. Your system.components.map() loop doesn't render anything. You can either (i) add a return statement inside the loop body or (ii) change the braces {} to parentheses (). e.g.
    {system.components.map((c, idx) => (
      <li key={idx}>Component: {c.name}</li>
    ))}
    

    Thirdly, I think adding the same defaultComponent to the array every time will create issues once you start editing components, since you're reusing the same object. I suggest adding a new object each time. i.e.

    function addComponent() {
      setSystem((system) => ({
        ...system,
        components: [...system.components, {
          name: "C1",
          comment: "",
        }],
      }));
    };