Распространенные ошибки хуков React

0 1

1. Изменение порядка вызова хуков

Содержание статьи:

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

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

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

Компонент SelectPerson принимает идентификатор человека, который должен быть получен через свойство prop и сохраняет личные данные в переменной состояния person с помощью хука useEffect().

Распространенные ошибки хуков React

React JS. Основы

Изучите основы ReactJS на практическом примере по созданию учебного веб-приложения

Получить курс сейчас!

JavaScript import { useState, useEffect } from “react”; function SelectPerson({ id }) { if (!id) { return <div>Please Select a Person </div>; } const [person, setPerson] = useState({ name: “”, role: “” }); useEffect(() => { const selectPerson = async () => { const response = await fetch(`/api/persons/${id}`); const selectedPerson = await response.json(); setPerson(selectedPerson); }; selectPerson(); }, [id]); return ( <div> <div>Name: {person.name}</div> <div>Description: {person.role}</div> </div> ); } export default function IndexPage() { const [personId, setPersonId] = useState(“”); return ( <div> <select onChange={({ target }) => setPersonId(target.value)}> <option value=””>Select a person</option> <option value=”1″>David</option> <option value=”2″>John</option> <option value=”3″>Tim</option> </select> <SelectPerson id={personId} /> </div> ); }

12345678910111213141516171819202122232425262728293031323334353637383940414243 import { useState, useEffect } from “react”; function SelectPerson({ id }) {  if (!id) {    return <div>Please Select a Person </div>;  }   const [person, setPerson] = useState({    name: “”,    role: “”  });   useEffect(() => {    const selectPerson = async () => {      const response = await fetch(`/api/persons/${id}`);      const selectedPerson = await response.json();      setPerson(selectedPerson);    };    selectPerson();  }, [id]);   return (    <div>      <div>Name: {person.name}</div>      <div>Description: {person.role}</div>    </div>  );} export default function IndexPage() {  const [personId, setPersonId] = useState(“”);  return (    <div>      <select onChange={({ target }) => setPersonId(target.value)}>        <option value=””>Select a person</option>        <option value=”1″>David</option>        <option value=”2″>John</option>        <option value=”3″>Tim</option>      </select>      <SelectPerson id={personId} />    </div>  );}

На первый взгляд, все выглядит нормально, но в консоли вы получите ошибку, поскольку useState() и useEffect() не доступны при каждом рендеринге.

Распространенные ошибки хуков React

Чтобы этого избежать, вы можете просто переместить оператор return после вызова хуков.

Итак, не забывайте использовать хуки перед любым ранним возвратом на верхнем уровне вашего компонента React.

JavaScript import { useState, useEffect } from “react”; function SelectPerson({ id }) { const [person, setPerson] = useState({ name: “”, role: “” }); useEffect(() => { const selectPerson = async () => { const response = await fetch(`/api/persons/${id}`); const selectedPerson = await response.json(); setPerson(selectedPerson); }; if (id) { selectPerson(); } }, [id]); if (!id) { return ‘Please Select a Person’; } return ( <div> <div>Name: {person.name}</div> <div>Description: {person.role}</div> </div> ); } …. … …

12345678910111213141516171819202122232425262728293031323334 import { useState, useEffect } from “react”; function SelectPerson({ id }) {   const [person, setPerson] = useState({    name: “”,    role: “”  });   useEffect(() => {    const selectPerson = async () => {      const response = await fetch(`/api/persons/${id}`);      const selectedPerson = await response.json();      setPerson(selectedPerson);    };    if (id) {       selectPerson();     }  }, [id]);  if (!id) {    return ‘Please Select a Person’;  }   return (    <div>      <div>Name: {person.name}</div>      <div>Description: {person.role}</div>    </div>  );} ……….

2. Использование useState, когда повторный рендеринг не требуется

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

Например, предположим, что у нас есть компонент с двумя кнопками. Первая кнопка запускает счетчик, а вторая — делает запрос на основе текущего состояния счетчика.

JavaScript import { useState } from “react”; function CounterCalc() { const [counter, setCounter] = useState(0); const onClickCounter = () => { setCounter((c) => c + 1); }; const onClickCounterRequest = () => { apiCall(counter); }; return ( <div> <button onClick={onClickCounter}>Counter</button> <button onClick={onClickCounterRequest}>Counter Request</button> </div> ); } export default function IndexPage() { return ( <div> <CounterCalc /> </div> ); }

12345678910111213141516171819202122232425262728 import { useState } from “react”; function CounterCalc() {  const [counter, setCounter] = useState(0);   const onClickCounter = () => {    setCounter((c) => c + 1);  };   const onClickCounterRequest = () => {    apiCall(counter);  };   return (    <div>      <button onClick={onClickCounter}>Counter</button>      <button onClick={onClickCounterRequest}>Counter Request</button>    </div>  );} export default function IndexPage() {  return (    <div>      <CounterCalc />    </div>  );}

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

Итак, если вам нужно использовать переменную внутри компонента, которая сохраняет свое состояние при визуализации без запуска повторной визуализации, useRef хук будет лучшим вариантом.

JavaScript import { useRef } from “react”; function CounterCalc() { const counter = useRef(0); const onClickCounter = () => { counter.current++; }; const onClickCounterRequest = () => { apiCall(counter.current); }; return ( <div> <button onClick={onClickCounter}>Counter</button> <button onClick={onClickCounterRequest}>Counter Request</button> </div> ); } export default function IndexPage() { return ( <div> <CounterCalc /> </div> ); }

12345678910111213141516171819202122232425262728 import { useRef } from “react”; function CounterCalc() {  const counter = useRef(0);   const onClickCounter = () => {    counter.current++;  };   const onClickCounterRequest = () => {    apiCall(counter.current);  };   return (    <div>      <button onClick={onClickCounter}>Counter</button>      <button onClick={onClickCounterRequest}>Counter Request</button>    </div>  );} export default function IndexPage() {  return (    <div>      <CounterCalc />    </div>  );}

3. Обработка действий с помощью useEffect

Хук useEffect один из самых привлекательных способов обработки задач, связанных с изменениями prop или state. Но могут быть ситуации, когда он еще больше усложняет ситуацию. Предположим, есть компонент, который принимает список данных и отображает его в DOM.

JavaScript import { useState, useEffect } from “react”; function PersonList({ onSuccess }) { const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [data, setData] = useState(null); const fetchPersons = () => { setLoading(true); apiCall() .then((res) => setData(res)) .catch((err) => setError(err)) .finally(() => setLoading(false)); }; useEffect(() => { fetchPersons(); }, []); useEffect(() => { if (!loading && !error && data) { onSuccess(); } }, [loading, error, data, onSuccess]); return <div>Person Data: {data}</div>; } export default function IndexPage() { return ( <div> <PersonList /> </div> ); }

1234567891011121314151617181920212223242526272829303132333435 import { useState, useEffect } from “react”; function PersonList({ onSuccess }) {  const [loading, setLoading] = useState(false);  const [error, setError] = useState(null);  const [data, setData] = useState(null);   const fetchPersons = () => {    setLoading(true);    apiCall()      .then((res) => setData(res))      .catch((err) => setError(err))      .finally(() => setLoading(false));  };   useEffect(() => {    fetchPersons();  }, []);   useEffect(() => {    if (!loading && !error && data) {      onSuccess();    }  }, [loading, error, data, onSuccess]);   return <div>Person Data: {data}</div>;} export default function IndexPage() {  return (    <div>      <PersonList />    </div>  );}

Первый хук useEffect обрабатывает начальный рендер apiCall(). Если все условия выполнены, второй useEffect вызовет функцию onSuccess().

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

Чтобы исправить ситуацию, мы должны переместить функцию onSucces() внутри функции apiCall(), не используя для этого отдельный хук.

JavaScript import { useState, useEffect } from “react”; function PersonList({ onSuccess }) { const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [data, setData] = useState(null); const fetchPersons = () => { setLoading(true); apiCall() .then((fetchedPersons) => { setData(fetchedPersons); onSuccess(); }) .catch((err) => setError(err)) .finally(() => setLoading(false)); }; useEffect(() => { fetchPersons(); }, []); return <div>Person Data: {data}</div>; } export default function IndexPage() { return ( <div> <PersonList /> </div> ); }

1234567891011121314151617181920212223242526272829303132 import { useState, useEffect } from “react”; function PersonList({ onSuccess }) {  const [loading, setLoading] = useState(false);  const [error, setError] = useState(null);  const [data, setData] = useState(null);   const fetchPersons  = () => {    setLoading(true);    apiCall()      .then((fetchedPersons) => {        setData(fetchedPersons);        onSuccess();      })      .catch((err) => setError(err))      .finally(() => setLoading(false));  };   useEffect(() => {    fetchPersons();  }, []);   return <div>Person Data: {data}</div>;} export default function IndexPage() {  return (    <div>      <PersonList />    </div>  );}

Распространенные ошибки хуков React

React JS. Основы

Изучите основы ReactJS на практическом примере по созданию учебного веб-приложения

Получить курс сейчас!

4. Использование устаревшего состояния

Если вы измените useState несколько раз в последующих строках кода, результаты могут вас удивить.

JavaScript import { useState } from “react”; function Counter({ onSuccess }) { const [count, setCount] = useState(0); console.log(count); const increaseCount = () => { setCount(count + 1); setCount(count + 1); setCount(count + 1); }; return ( <> <button onClick={increaseCount}>Increase</button> <div>Counter: {count}</div> </> ); } export default function IndexPage() { return ( <div> <Counter /> </div> ); }

123456789101112131415161718192021222324252627 import { useState } from “react”; function Counter({ onSuccess }) {  const [count, setCount] = useState(0);  console.log(count);   const increaseCount = () => {    setCount(count + 1);    setCount(count + 1);    setCount(count + 1);  };   return (    <>      <button onClick={increaseCount}>Increase</button>      <div>Counter: {count}</div>    </>  );} export default function IndexPage() {  return (    <div>      <Counter />    </div>  );}

В приведенном выше примере компонент Counter увеличивает значение переменной count. Поскольку функция increaseCount() вызывает функцию setCount() 3 раза, вы можете подумать, что одно нажатие кнопки увеличит значение count на 3. Но при каждом нажатии кнопки оно будет увеличиваться только на 1.

Первоначальный вызов setCount(count + 1) увеличивает соответствующее значение count, так как count + 1 = 0 + 1 = 1. Точно так же следующие два вызова setCount(count + 1) также устанавливают count равным 1, поскольку он использует устаревшее состояние count.

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

JavaScript import { useState } from “react”; function Counter({ onSuccess }) { const [count, setCount] = useState(0); console.log(count); const increaseCount = () => { setCount(count => count + 1); setCount(count => count + 1); setCount(count => count + 1); }; return ( <> <button onClick={increaseCount}>Increase</button> <div>Counter: {count}</div> </> ); } export default function IndexPage() { return ( <div> <Counter /> </div> ); }

123456789101112131415161718192021222324252627 import { useState } from “react”; function Counter({ onSuccess }) {  const [count, setCount] = useState(0);  console.log(count);   const increaseCount = () => {    setCount(count => count + 1);    setCount(count => count + 1);    setCount(count => count + 1);  };   return (    <>      <button onClick={increaseCount}>Increase</button>      <div>Counter: {count}</div>    </>  );} export default function IndexPage() {  return (    <div>      <Counter />    </div>  );}

5. Отсутствие зависимостей useEffect

Хук useEffect — один из наиболее часто используемых хуков React, и по умолчанию он всегда запускается при каждом повторном рендеринге. Однако такое поведение может вызвать проблемы с производительностью.

Вы можете избежать нежелательных визуализаций, передав массив зависимостей в хук useEffect.

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

JavaScript useEffect(() => { showCount(count); }, [count]); // Only re-run the effect if count changes in the preceding render

123 useEffect(() => {  showCount(count);}, [count]); // Only re-run the effect if count changes in the preceding render

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

JavaScript import { useState, useEffect} from “react”; function Counter() { const [count, setCount] = useState(0); const showCount = (count) => { console.log(“Show Count”, count); }; useEffect(() => { showCount(count); }, []); return ( <> <div>Hello..!!</div> <div>Counter: {count}</div> </> ); } export default function IndexPage() { return ( <div> <Counter /> </div> ); }

12345678910111213141516171819202122232425262728 import { useState, useEffect} from “react”; function Counter() {  const [count, setCount] = useState(0);   const showCount = (count) => {    console.log(“Show Count”, count);  };   useEffect(() => {    showCount(count);  }, []);   return (    <>      <div>Hello..!!</div>      <div>Counter: {count}</div>    </>  );} export default function IndexPage() {  return (    <div>      <Counter />    </div>  );}

Но есть проблема. Вы можете увидеть следующее предупреждение в консоли DevTools:

Распространенные ошибки хуков React

Примечание: несмотря на то, что DevTools просит вас удалить или сохранить массив зависимостей, вы не должны удалять его, поскольку это будет хак,чтобы обмануть React относительно зависимостей.

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

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

JavaScript import { useState, useEffect} from “react”; function Counter() { const [count, setCount] = useState(0); const showCount = (count) => { console.log(“Show Count”, count); }; useEffect(() => { showCount(count); }, [count]); return ( <> <div>Hello..!!</div> <div>Counter: {count}</div> </> ); } export default function IndexPage() { return ( <div> <Counter /> </div> ); }

12345678910111213141516171819202122232425262728 import { useState, useEffect} from “react”; function Counter() {  const [count, setCount] = useState(0);   const showCount = (count) => {    console.log(“Show Count”, count);  };   useEffect(() => {    showCount(count);  }, [count]);   return (    <>      <div>Hello..!!</div>      <div>Counter: {count}</div>    </>  );} export default function IndexPage() {  return (    <div>      <Counter />    </div>  );}

Подведение итогов

React Hooks — это простые и понятные функции многократного использования, которые можно включить в ваш рабочий процесс. Кроме того, они предлагают нам множество вариантов для разнообразного использования и нам не нужно создавать их с нуля.

Но разработчики склонны совершать серьезные ошибки при использовании React Hooks с расширенными пользовательскими функциями. В этой статье я обсудил 5 таких распространенных ошибок, которых следует избегать разработчикам при использовании React Hooks. Спасибо за чтение !!!

Автор: Piumi Liyana Gunawardhana

Источник: webformyself.com

Оставьте ответ