Реагировать Слишком много повторных отрисовок при использовании ловушки setState внутри функции обратного вызова

Когда я использую ловушку setState внутри функции, которую я использую в качестве обратного вызова для компонента, я получаю ошибку Too many re-renders from React.

Ниже приведен бесполезный пример, демонстрирующий, что я пытаюсь сделать.

У меня есть массив объектов Person и массив Pets. У каждого питомца есть хозяин (= человек). Я хочу показать имя человека и его питомца.

Я перебираю массив Persons, и для каждого человека должен быть TestComponent. TestComponent ожидает имя человека и имя питомца.

Имя питомца вычисляется в функции обратного вызова getPetByOwnerName, которая перебирает всех домашних животных, и если данное имя человека совпадает с именем владельца домашнего питомца, оно вернет это имя питомца.

ЕСЛИ у Person нет домашних животных, он должен добавить пустой объект Pet в список Pets, используя setPets().

setPets() внутри функции getPetByOwnerName вызывает ошибку Too many re-renders.

Возможно, в этом примере нет смысла делать это таким образом, но это для воссоздания проблемы, которая у меня есть в более крупном проекте.

import React, { useState } from 'react'
import TestComponent from './TestComponent'

const TestPage = () => {

    const [persons, setPersons] = useState([{name: 'Tom', age: 35}, {name: 'Fred', age: 50 }])

    const [pets, setPets] = useState([{owner: 'Tom',name: 'Doggo'}])


    const getPetByOwnerName = (name) => {
        let petName = null
        pets.map((pet, index) => {
            if (pet.owner === name) {
                petName = pet.name
            }
            return pet
        })

        if (!petName) {
            const pet = {
                owner: name,
                name: ''
            }
            const updatedPets = pets
            pets.push(pet)
            setPets(updatedPets)
        }

        return petName
    }

    return (
        <React.Fragment>
            {
                persons.map((person, index) => (
                    <TestComponent key={index} personName={person.name} petName={getPetByOwnerName(person.name)}></TestComponent>
                ))
            }
        </React.Fragment>
    )
}

export default TestPage

TestComponent:

import React from 'react'

const TestComponent = ({ personName, petName }) => {

    return (
        <React.Fragment>
            <p>{personName} owns pet: {petName}</p>
        </React.Fragment>
    )
}

export default TestComponent


person Walt    schedule 20.05.2020    source источник


Ответы (1)


Вероятно, лучше обрабатывать такую ​​функциональность внутри эффекта использования. Вы можете следить за изменениями в людях, а затем соответствующим образом обновлять питомцев. Что-то вроде ниже.

Но ваша настоящая проблема находится в этой строке if (!petName). Когда petName - пустая строка, она попадает сюда и устанавливает состояние навсегда. Вам нужен чек типа if(!petName && petName !== '')

    useEffect(() => {
      persons.map(person => {
        const petIndex = pets.findIndex(p => p.owner === person.name);

          if (petIndex === -1) {
            const pet = {
              owner: person.name,
              name: ''
          }
          setPets(prevPets => ([...prevPets, pet]))
        }
      })
    }, [persons]);

Также в качестве побочного примечания при использовании предыдущего состояния для обновления состояния лучше использовать версию обратного вызова (см. Обновленный набор домашних животных в примере выше)

Поскольку это всего лишь пример вашей реальной проблемы, вероятно, лучше всего поставить debugger; непосредственно перед тем, как вы установите свое состояние, и посмотрите, почему вы устанавливаете его так часто.

person topched    schedule 20.05.2020