skip to Main Content

using React I’ve been able to update a value of an object stored in the parent component from a onChange() made in a child component.

However I’m trying to do the same with a component that is created from a list of mapped objects, so it only updates the value in the object list that built that component. I’m failing but here’s an example of what I’m doing:

Parent Component:

const Parent = () => {
  const [banners, bannersUpdate] = React.useState([
    {
      id: 1, name: "banner1", nameSize: 14
    },
    {
      id: 2, name: "banner2", nameSize: 16
    },
    {
      id: 3, name: "banner3", nameSize: 18
    }
  ]);
  
  
  const handleUpdateNameSize = (id, e) => {
    banners.nameSize = e.value
    bannersUpdate({
        nameSize: banners.nameSize,
        ...banners
    })
  }
  
  
  return(
    <section className="banners">
      {banners.map(banner =>
        <Banner 
          key={banner.id}
          id={banner.id}
          name={banner.name}
          nameSize={banner.nameSize}
          updateNameSize={handleUpdateNameSize}
        />
      )}
    </section>
  );
}

Child component:

const Banner = (props) => {
  return (
    <div className="banCont">
      <h2 style={{fontSize: props.nameSize + 'px'}}>{props.name}</h2>
      <input onChange={(e) => props.updateNameSize(props.id, e.target)}  className={'slider1-' + props.name} />
    </div>
  )
}

With the above it doesn’t update the object item relating to that banner, it instead just adds a new object item at the top level with that name and value.

I believe the answer relies somewhere in using the key and I’ve tried replacing all the banners with banners[id] like: banners[id].nameSize = e.value but for some reason it’s not working to update the value.

Help would be appreciated please, thanks.

2

Answers


  1. const Parent = () => {
      const [banners, setBanners] = React.useState([
        {
          id: 1, name: "banner1", nameSize: 14
        },
        {
          id: 2, name: "banner2", nameSize: 16
        },
        {
          id: 3, name: "banner3", nameSize: 18
        }
      ]);
    
      const handleUpdateNameSize = (id, value) => {
        setBanners(prevBanners =>
          prevBanners.map(banner =>
            banner.id === id ? { ...banner, nameSize: value } : banner
          )
        );
      };
    
      return (
        <section className="banners">
          {banners.map(banner => (
            <Banner 
              key={banner.id}
              id={banner.id}
              name={banner.name}
              nameSize={banner.nameSize}
              updateNameSize={handleUpdateNameSize}
            />
          ))}
        </section>
      );
    };
    
    const Banner = (props) => {
      return (
        <div className="banCont">
          <h2 style={{ fontSize: props.nameSize + 'px' }}>{props.name}</h2>
          <input
            type="range"
            min="10"
            max="50"
            value={props.nameSize}
            onChange={(e) => props.updateNameSize(props.id, e.target.value)}
            className={'slider1-' + props.name}
          />
        </div>
      );
    };
    

    Problem:

    The problem lies in the incorrect approach to updating the state of a nested object within an array in React. The initial attempt involved directly modifying the array (banners) and then attempting to update the state with an incomplete and incorrect structure, resulting in state inconsistency and unexpected behavior. In React, state updates must be performed immutably to ensure proper reactivity and rendering. This means creating a new array that includes the updated object rather than mutating the existing array directly.

    You can read more about this here https://reacttraining.com/blog/state-in-react-is-immutable

    Login or Signup to reply.
  2. your updating function has some issues. First banners.nameSize = e.value you are trying to add a nameSize "field" to your banners "array". Instead what you should do is to find the relevant banner using the id and update its nameSize value.
    Also as the other answer mentions as well. Use map to ensure that the state is changed and the component re-renders. Using a find by the id and then updating the nameSize field will mutate the state and will not cause a re-render

    Also better to follow naming standards for useState updating function. if state is banners then the function name like setBanners

    const Banner = ({id, name, nameSize, updateNameSize}) => {
      return (
        <div className="banCont">
          <h2 style={{fontSize: nameSize + 'px'}}>{name}</h2>
          <input onChange={(e) => updateNameSize(id, e.target)}  className={'slider1-' + name} />
        </div>
      )
    }
    
    const Parent = () => {
        const [banners, setBanners] = React.useState([
        { id: 1, name: "banner1", nameSize: 14 },
        { id: 2, name: "banner2", nameSize: 16 },
        { id: 3, name: "banner3", nameSize: 18 }
      ]);
      
      const handleUpdateNameSize = (id, e) => {
         const nameSize = e.value.length;
         setBanners(prevBanner => prevBanner.map(banner => banner.id === id ? { ...banner, nameSize } : banner))
      }
      
      return(
        <div style={{display:'flex'}}>
          <section className="banners">
            {banners.map(banner =>
              <Banner 
                key={banner.id}
                id={banner.id}
                name={banner.name}
                nameSize={banner.nameSize}
                updateNameSize={handleUpdateNameSize}
              />
            )}
          </section>
          <pre>{JSON.stringify(banners, null, 2)}</pre>
        </ div>
      );
    };
    
    
    const App = () => {
      return (
         <Parent />
      );
    };
    ReactDOM.render(<App />, document.getElementById("app"));
    <script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
    <script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
    <div id="app"></div>
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search