Как обрабатывать столкновения фильтров состояния массива

#arrays #reactjs #asynchronous #filter #state

#массивы #reactjs #асинхронный #Фильтр #состояние

Вопрос:

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

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

 fileHandler = (index, event) =>{
    let incompleteFiles = this.state.incompleteFiles
    incompleteFiles[index].loading = true
    incompleteFiles[index].file = event.target.files[0]
    this.setState({ incompleteFiles: incompleteFiles },()=>{
            const fileData = new FormData()
            fileData.append('file', event.targets[0].file)
            let incompleteFiles = this.state.incompleteFiles
            let completeFiles = this.state.completeFiles
                api.uploadFile(fileData)
                    .then(res=>{
                        if(res.data.success){
                            this.setState(state=>{
                                let completeFile = {
                                    name : res.data.file.name,
                                }
                                completeFiles.push(completeFile)
                                incompleteFiles = incompleteFiles.filter(inc=>inc.label !== res.data.file.name)
                                return{
                                    completeFiles,
                                    incompleteFiles
                                }
                            })
                        }
                    })
        })
    }
 

Обновлено принятым ответом с незначительной настройкой

 fileHandler = (index, event) =>{
    this.setState(({ incompleteFiles }) => ({       
    //  Update the state in an immutable way.       
        incompleteFiles: [              
            ...incompleteFiles.slice(0, index),              
            {                
                ...incompleteFiles[index],                
                loading: true,                
                file: event.target.files[0],              
            },              
            ...incompleteFiles.slice(index 1)       
        ],     
    }),  () => {
      const fileData = new FormData()
      fileData.append('file', event.targets[0].file)
      api.uploadFile(fileData)
        .then(res => {
          if(res.data.success){
              this.setState(({ incompleteFiles, completeFiles }) => ({
                completeFiles: [
                  ...completeFiles, // Again, avoiding the .push since it mutates the array.
                  { // The new file.
                    name: res.data.file.name,
                  }
                ],
                incompleteFiles: incompleteFiles.filter(inc=>inc.label !== res.data.file.name),
              })))
          }
        })
    });
  }
 

Ответ №1:

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

 //  Bad
this.setState({ counter: this.state.counter   1 });

// Good
this.setState((currentState) => ({ counter: currentState.counter   1 }));
 

Это гарантирует, что вы получаете самую последнюю версию состояния. Тот факт, что это необходимо, является побочным эффектом того, как состояние пулов React обновляется под капотом (что делает его более производительным).

Я думаю, что если бы вы переписали свой код, чтобы использовать этот шаблон, это было бы что-то вроде этого:

 fileHandler = (index, event) =>{
    this.setState(({ incompleteFiles }) => ({
      //  Update the state in an immutable way.
      incompleteFiles: {
        [index]: {
          ...incompleteFiles[index],
          loading: true,
          file: event.target.files[0],
        },
      },
    }), () => {
      const fileData = new FormData()
      fileData.append('file', event.targets[0].file)
      api.uploadFile(fileData)
        .then(res => {
          if(res.data.success){
              this.setState(({ incompleteFiles, completeFiles }) => ({
                completeFiles: [
                  ...completeFiles, // Again, avoiding the .push since it mutates the array.
                  { // The new file.
                    name: res.data.file.name,
                  }
                ],
                incompleteFiles: incompleteFiles.filter(inc=>inc.label !== res.data.file.name),
              })))
          }
        })
    });
  }
 

Еще одна вещь, которую следует иметь в виду, — избегать изменения ваших объектов состояния. Такие методы, как Array.push будут изменять массив на месте, что может вызвать проблемы и головные боли.

Комментарии:

1. это работает довольно хорошо, мне пришлось немного изменить часть для обновления состояния объекта в массиве, чтобы javascript this.setState(({ incompleteFiles }) => ({ // Update the state in an immutable way. incompleteFiles: [ ...incompleteFiles.slice(0, index), { ...incompleteFiles[index], loading: true, file: event.target.files[0], }, ...incompleteFiles.slice(index 1) ], })

Ответ №2:

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

 fileHandler = async (index, event) =>{
    const incompleteFiles = [...this.state.incompleteFiles]
    incompleteFiles[index].loading = true
    incompleteFiles[index].file = event.target.files[0]

    this.setState(
      {
        incompleteFiles
      },
      async (prev) => {
        const fileData = new FormData()
        fileData.append('file', event.targets[0].file)

        const res = await api.uploadFile(fileData)

        /// set loading state to false
        incompleteFiles[index].loading = false
        
        if (!res.data.success) {
          return { ...prev, incompleteFiles }
        }

        // add new file name into completeFiles and remove uploaded file name from incompleteFiles
        return {
          ...prev,
          completeFiles: [...prev.completeFiles, { name : res.data.file.name }],
          incompleteFiles: incompleteFiles.filter(inc=>inc.label !== res.data.file.name)
        }
      })
    )
}