Превышена максимальная глубина обновления с помощью useLayoutEffect, useRef

#reactjs #react-hooks

#reactjs #реагирующие хуки

Вопрос:

У меня есть следующий компонент:

 // component.js
import React from 'react';
import useComponentSize from 'useComponentSize';

const component = () => {
  const [comSize, comRef] = useComponentSize();

  return (
    <div style={{width: '100%'}} ref={comRef}>
      <p>hi</p>
    </div>
  );
};
  

который использует useComponentSize , сделанный мной хук:

 // useComponentSize.js
import {
  useRef,
  useState,
  useLayoutEffect,
} from 'react';

const useComponentSize = () => {
  const [size, setSize] = useState({
    width: 0,
    height: 0,
  });
  const resizeRef = useRef();

  useLayoutEffect(() => {
    setSize(() => ({ 
      width: resizeRef.current.clientWidth,
      height: resizeRef.current.clientHeight,
    }));
  });
  
  return [size, resizeRef];
};

export default useComponentSize;
  

но по какой-то причине, которую я не могу понять, она всегда превышает максимальную глубину обновления. Я попытался useLayoutEffect зависеть от resizeRef , что, как я думал, сработает, но затем он снова не обновляется (что, по размышлении, именно так, как я должен был ожидать, что ссылка будет работать).

Что я должен сделать, чтобы это работало правильно, и, самое главное, почему вышеупомянутое вызывает бесконечный цикл?


Редактировать: вторая попытка с использованием прослушивателей событий, по-прежнему неудачная. Какую концепцию я здесь упускаю?

 // component.js
import React, { useRef } from 'react';
import useComponentSize from 'useComponentSize';

const component = () => {
  const ref = useRef();
  const [comSize] = useComponentSize(ref);

  return (
    <div style={{width: '100%'}} ref={ref}>
      <p>hi</p>
    </div>
  );
};
  
 import {
  useRef,
  useState,
  useLayoutEffect,
} from 'react';

const useComponentSize = (ref) => {
  const [size, setSize] = useState();

  useLayoutEffect(() => {
    const updateSize = () => {
      setSize(ref.current.clientWidth);
    }
    
    ref.current.addEventListener('resize', updateSize);
    updateSize();
    
    return () => window.removeEventListener('resize', updateSize);
  }, []);
  
  return [size];
};

export default useComponentSize;
  

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


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

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

1. Вы пробовали useEffect вместо useLayoutEffect ?

2. Только что пробовал — без кубиков

3. @pilchard Я думаю, что это используется для уничтожения. Позвольте мне еще раз проверить документы!

4. @Yousaf, я тоже пробовал ваш метод, и он обновляется только в первый раз. Он не обновляется при изменении размера компонента!

5. Это решение не отслеживает изменения в элементах DOM. Вы можете использовать ResizeObserver , чтобы получить начальный размер, а также наблюдать за изменениями. Попробуйте эту демонстрацию

Ответ №1:

Ваш код застревает в бесконечном цикле, потому что вы не передали массив зависимостей в useEffectLayout hook.

На самом деле вам вообще не нужно использовать useEffectLayout hook. Вы можете наблюдать за изменениями в элементе DOM с помощью ResizeObserver API.

P.S: Хотя проблема OP уже решена с помощью демонстрации, опубликованной в одном из комментариев под вопросом, я публикую ответ для всех, кто может рассмотреть этот вопрос в будущем.

Пример:

 const useComponentSize = (comRef) => {
  const [size, setSize] = React.useState({
    width: 0,
    height: 0
  });

  React.useEffect(() => {
    const sizeObserver = new ResizeObserver((entries, observer) => {
      entries.forEach(({ target }) => {
        setSize({ width: target.clientWidth, height: target.clientHeight });
      });
    });
    sizeObserver.observe(comRef.current);

    return () => sizeObserver.disconnect();
  }, [comRef]);

  return [size];
};

function App() {
  const comRef = React.useRef();
  const [comSize] = useComponentSize(comRef);

  return (
    <div>
      <textarea ref={comRef} placeholder="Change my size"></textarea>
      <h1>{JSON.stringify(comSize)}</h1>
    </div>
  );
}

ReactDOM.render(<App />, document.getElementById("root"));  
 <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script>
<div id="root"></div>  

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

1. Вы полностью правы в том, что useLayoutEffect я предполагал, что мне нужно useEffect отслеживать изменения, и useLayoutEffect поскольку это необходимо, сначала отобразите ссылку. В частности, спасибо за исправление и указание мне на наблюдателя