Как использовать useEffect для изменения состояния использования при каждом изменении var без запуска бесконечного цикла?

#reactjs

Вопрос:

в моем компоненте react в моей консоли возникает следующая проблема

index.js:1 Warning: Maximum update depth exceeded. This can happen when a component calls setState inside useEffect, but useEffect either doesn't have a dependency array, or one of the dependencies changes on every render. at Id (http://localhost:3000/main.82c936520e7a7a921612.hot-update.js:32:27) at div at div at div at App (http://localhost:3000/static/js/main.chunk.js:249:62)

Время, в течение которого отображается ошибка, увеличивается с каждой новой секундой, и та же ошибка создается снова. Я понятия не имею, почему, потому что я думал, что моя логика имеет смысл, как она создает бесконечный цикл?

 import React, { useEffect } from "react";
import ReactTooltip from "react-tooltip";

import { FaQuestionCircle, FaRedoAlt } from 'react-icons/fa';

interface Props {
    setId: React.Dispatch<React.SetStateAction<{data: number, componentRef: React.RefObject<HTMLDivElement>}>>;
    id : {data: number, componentRef:React.RefObject<HTMLDivElement>};
}

const Id : React.FC<Props> = ( props : Props ) => {
    
    var idData = {...props.id};

    useEffect(()=>{
        props.setId(idData);
    }, [idData]);

    const generateNewId = () => {
        idData.data = Math.floor( Math.random()*1000 )   1; 
    }

    useEffect(()=>{
        generateNewId();
    },[])

    const modifyId = (sign : string) => {
        sign === " " ? idData.data = idData.data   1 : idData.data = idData.data - 1 ;
    }

    return(
        <div>
            <ReactTooltip id="counter-info">
                <p>A random ID is generated each time you click refresh</p>
                <p className="text-bold">What is ID?</p>
                <p className="text-bold">Your ID is added to the back of your username, to always ensure that you have a unique username</p>
            </ReactTooltip> 
            <h2>Your ID: {idData.data}
                <span data-tip data-for="counter-info">
                    <FaQuestionCircle/>
                </span>
            </h2>
            <button className="btn rounded-circle btn-dark" onClick={()=>modifyId("-")}>-</button>
            <button className="btn rounded-circle btn-dark mr-3" onClick={()=>modifyId(" ")}> </button>
            <button className="btn" onClick={()=>generateNewId()}><FaRedoAlt/></button>
        </div>
    )
    
}

export default Id;
 

В приложении.tsx

 const idRef = React.useRef<HTMLDivElement>(null);
const [ id, setId ] = useState<{data: number, componentRef: React.RefObject<HTMLDivElement>}>(
    {
      data: NaN,
      componentRef: idRef
    }
  )
<div ref={idRef} className="col-3">
          <Id id={id} setId={setId}/>
        </div>
 

Ответ №1:

Проблемы

  1. Вы устанавливаете идентификатор, переданный вашему реквизиту, с помощью функции настройки, переданной вашему реквизиту: (Таким образом, каждый раз, когда этот компонент выполняет рендеринг, он устанавливает идентификатор, а поскольку идентификатор является реквизитом, запускается повторная рендеринг. Следовательно, бесконечный цикл рендеринга)
     var idData = {...props.id};

    useEffect(()=>{
        props.setId(idData);
    }, [idData]);
 

В этом нет необходимости, так как это контролируемый компонент, и вы уже передали идентификатор своему ребенку.

  1. Вы изменяете реквизиты непосредственно в своей modifyId функции. Вместо этого назначьте его переменной состояния и внесите изменения в эту переменную состояния

Понимание массива зависимостей

В useEffect массиве зависимостей убедитесь, что этот конкретный эффект использования срабатывает только в зависимости от вашего массива зависимостей

 useEffect(() => console.log("run once"), [])
useEffect(() => console.log("run only id changes"), [id])
 

Решение вашей проблемы

Ваша проблема немного запутана для понимания. Исходя из моего понимания, вы хотите, чтобы setId , когда вы modifyId :

 const Id : React.FC<Props> = ( props : Props ) => {
    
    const {id: idData} = props
    const [childId, setChildId] = useState({})

    useEffect(() => {
       if(idData) setChildId(idData)
    }, [idData])

    const generateNewId = () => {
        setChildId(Math.floor( Math.random()*1000 )   1)
    }

    const modifyId = (sign : string) => {
        setChildId(id => sign === ' ' ? {...id, data: id.data   1} : {...id, data: id.data-1})
    }

    return(
        <div>
            <ReactTooltip id="counter-info">
                <p>A random ID is generated each time you click refresh</p>
                <p className="text-bold">What is ID?</p>
                <p className="text-bold">Your ID is added to the back of your username, to always ensure that you have a unique username</p>
            </ReactTooltip> 
            <h2>Your ID: {childId.data}
                <span data-tip data-for="counter-info">
                    <FaQuestionCircle/>
                </span>
            </h2>
            <button className="btn rounded-circle btn-dark" onClick={()=>modifyId("-")}>-</button>
            <button className="btn rounded-circle btn-dark mr-3" onClick={()=>modifyId(" ")}> </button>
            <button className="btn" onClick={()=>generateNewId()}><FaRedoAlt/></button>
        </div>
    )
    
}

export default Id;
 

PS: Если вы не хотите устанавливать переменную состояния, удалите useState вызов и замените setChildId на prop.setId

Ответ №2:

я вижу несколько ошибок в вашем коде; передача объекта в качестве зависимости useEffect не является хорошей идеей, объекты в js являются ссылочным типом, когда ваш компонент запускается снова (повторный рендеринг), каждый раунд idData объект будет воссоздан заново…

поэтому, когда компонент запускается снова, текущий объект не соответствует предыдущему объекту, который вы передали в массив useEffect зависимостей, и useEffect запускается снова… часто этот процесс будет повторяться постоянно!

например:

 {} === {} //flase!
 

в вашем случае вы должны использовать setId , когда хотите установить новый идентификатор… или, если вам нужно его инициализировать, вы можете установить пустой массив в качестве useEffect зависимости.

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

1. Как это может {} !== {} быть, если объекты имеют одинаковые ключи и значения? Пытаюсь понять

2. @argonx вам нужно выполнить глубокое равенство для сравнения двух объектов, для этого вы можете использовать этот инструмент: npmjs.com/package/deep-equal