React DOM не обновляется при изменении состояния

У меня есть компонент React, который принимает произвольное количество файлов, каждый на своем языке. Я хочу иметь кнопку, которая позволяет пользователю добавлять столько файлов, сколько они хотят, каждый со своим собственным раскрывающимся меню языков. На данный момент я исключил загрузку файлов, так как изолирую проблему, которая у меня возникла.

Я уже создал довольно много подобных компонентов без проблем, но по какой-то причине у меня большие проблемы с этим. Когда я меняю язык в раскрывающемся списке, я консолью состояние журнала и вижу, что язык обновлен в состоянии, но он не обновляется в DOM в моем методе карты.

Вот мой код

function App() {
  const [files, setFiles] = useState([]);

  const languages = ["English", "Spanish", "Arabic", "French"];

  const handleAddFile = e => {
    let newFiles = files;
    newFiles.push({
      content: null,
      preview: null,
      language: ""
    });
    setFiles(newFiles);
  };

  const changeLanguage = (event, index) => {
    let newFiles = files;
    newFiles[index].language = event.target.value;
    setFiles(newFiles);
  };

  console.log(files);

  return (
    <div>
      <p>Files</p>
      <p>Upload a file for as many languages as you have</p>
      {files.map((file, index) => (
        <div key={index}>
          <p>current language: {file.language}</p>
          <select
            value={file.language}
            onChange={event => changeLanguage(event, index)}
          >
            <option value="" disabled>
              Select a language
            </option>
            {languages.map((lang, i) => (
              <option value={lang} key={i}>
                {lang}
              </option>
            ))}
          </select>
        </div>
      ))}
      <button onClick={e => handleAddFile(e)}>+ Add File</button>
    </div>
  );
}

Я также сделал codeandbox, чтобы воспроизвести проблему, но на самом деле это еще больше усложняет ситуацию. Когда я запускаю код локально, я могу легко добавлять экземпляры файлов, но язык в элементе select не обновляется в DOM ни для одного экземпляра. В codeandbox ничего не обновляется в DOM, если я не внесу правку в редакторе, а затем все обновится так, как я ожидал, включая язык. Во всех случаях состояние регистрируется в консоли точно так, как я ожидал. Компонент в песочнице идентичен тому, что есть у меня локально, хотя локально он встроен в другой компонент.

https://codesandbox.io/s/ed4fx?file=/src/App.js:0-1248


person 7evam    schedule 15.05.2020    source источник
comment
Я раздвоил ваши коды и ящик codesandbox.io/s/hungry- gagarin-il35g? file = / src / App.js, дайте мне знать, сработало ли это для вас.   -  person Learner    schedule 15.05.2020
comment
@ 7evam И handleAddFile, и changeLanguage изменяют состояние. Это предотвратит повторную визуализацию, потому что response не увидит, что состояние изменилось.   -  person Brian Thompson    schedule 15.05.2020
comment
Я добавил часть обновления кода и пояснение в ответ   -  person Learner    schedule 15.05.2020


Ответы (1)


Проблема заключается в обоих методах handleAddFile и changeLanguage, вы напрямую обращаетесь к файлам значений состояния, поэтому для обновления вам нужно взять копию, внести изменения и установить новые значения.

Обновления, которые вам нужно сделать в обоих методах

let newFiles = files;

To

let newFiles = [...files];

Как предлагает @Brian, при обновлении глубоко вложенных значений, таких как язык, в методе changeLanguage. вы можете сделать что-нибудь, как показано ниже, чтобы состояние не изменилось.

const changeLanguage = (event, index) => {
  let newFiles = [...files];
  newFiles[index] = { ...newFiles[index], language: event.target.value };
  setFiles(newFiles);
};

Отметьте этот рабочий codeandbox

Лучшее объяснение здесь

person Learner    schedule 15.05.2020
comment
changeLanguage также должен учитывать глубоко вложенные значения состояния. Даже после правильной деструктуризации files выполнение newFiles[index].language = event.target.value; приведет к изменению вложенного объекта (даже если это, вероятно, не предотвратит повторный рендеринг) - person Brian Thompson; 15.05.2020
comment
да, это работает! Я знал, что не могу напрямую мутировать состояние, но теперь я понимаю, что я сам того не знал. Спасибо за помощь! - person 7evam; 15.05.2020
comment
В этом ответе под заголовком "Пояснение" - довольно подробное объяснение того, как делать настоящие копии объектов (и массивов) в JS. - person Brian Thompson; 15.05.2020
comment
@BrianThompson, я надеюсь, что это именно то, что вы ожидали, поправьте меня, добавлю в ответ let newFiles = [... files]; пусть currentFile = newFiles [индекс]; const {значение} = event.target currentFile = {... currentFile, язык: значение}; newFiles [индекс] = currentFile setFiles (newFiles); - person Learner; 15.05.2020
comment
Это кажется излишне сложным, но я думаю, что это работает - person Brian Thompson; 15.05.2020
comment
как вы предложили обновить его, даже я тоже изначально подумал, но для лучшего понимания вы можете обновить его. Для будущих ссылок это будет легко - person Learner; 15.05.2020
comment
круто, и спасибо, добавили ссылку на часть объяснения, которая у вас есть. Удачного кодирования :) # оставайтесь дома # оставайтесь в безопасности - person Learner; 15.05.2020