Реагировать на обновление значения в сопоставленном объекте из дочернего компонента

используя React, я смог обновить значение объекта, хранящегося в родительском компоненте, из onChange(), созданного в дочернем компоненте.

Однако я пытаюсь сделать то же самое с компонентом, созданным из списка сопоставленных объектов, поэтому он обновляет только значение в списке объектов, который создал этот компонент. У меня ничего не получается, но вот пример того, что я делаю:

Родительский компонент:

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

Дочерний компонент:

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>
  )
}

Учитывая вышеизложенное, он не обновляет элемент объекта, относящийся к этому баннеру, а просто добавляет новый элемент объекта на верхний уровень с этим именем и значением.

Я считаю, что ответ где-то зависит от использования key, и я пробовал заменить все banners на banners[id], например: banners[id].nameSize = e.value, но по какой-то причине обновление значения не работает.

Помощь будет оценена, пожалуйста, спасибо.

🤔 А знаете ли вы, что...
JavaScript используется для разработки современных одностраничных приложений (SPA), где весь контент загружается асинхронно.


1
62
2

Ответы:

Решено
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>
  );
};

Проблема:

Проблема заключается в неправильном подходе к обновлению состояния вложенного объекта внутри массива в React. Первоначальная попытка включала непосредственное изменение массива (баннеров), а затем попытку обновления состояния с неполной и неправильной структурой, что приводило к несогласованности состояния и неожиданному поведению. В React обновления состояния должны выполняться неизменно, чтобы обеспечить надлежащую реактивность и рендеринг. Это означает создание нового массива, включающего обновленный объект, а не непосредственное изменение существующего массива.

Подробнее об этом можно прочитать здесь https://reacttraining.com/blog/state-in-react-is-immutable


у вашей функции обновления есть некоторые проблемы. Сначала banners.nameSize = e.value вы пытаетесь добавить nameSize «поле» в «массив» своих баннеров. Вместо этого вам следует найти соответствующий баннер с помощью id и обновить его значение nameSize.
Также, как упоминается в другом ответе. Используйте map, чтобы убедиться, что состояние изменено и компонент повторно визуализируется. Использование find по идентификатору и последующее обновление поля nameSize приведет к изменению состояния и не приведет к повторному рендерингу.

Также лучше следовать стандартам именования для функции обновления useState. если состояние banners, то имя функции типа 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>