Головна / Статті / Перестаньте постійно використовувати useEffect для кожної проблеми у React.

Перестаньте постійно використовувати useEffect для кожної проблеми у React.

Ефекти синхронізуються з зовнішніми системами; генеруються під час рендерингу, обробляють кліки у подіях, отримуються за допомогою інструментів з реальними даними.

1424 слів

Пастка useEffect

Учні, які вивчають хуки, часто вдаються до useEffect для отримання даних, створення похідного стану та обробки натискань кнопок. Ефекти синхронізуються з зовнішніми системами — вони не є стандартним інструментом для звичайної логіки відображення.

Що насправді робить useEffect

Після малювання елементів React виконує ефекти для підключення слухачів, відкриття з’єднань чи безпосереднього взаємодіяння з чимось поза межами React. Неправильне використання призводить до мерехтіння, подвійного отримання даних та інших проблем.

// ❌ BAD: Two renders, unnecessary state
function UserProfile({ firstName, lastName }) {
  const [fullName, setFullName] = useState('');
useEffect(() => {
    setFullName(`${firstName} ${lastName}`);
  }, [firstName, lastName]);
  return <h1>{fullName}</h1>;
}
// ✅ GOOD: Calculate during render
function UserProfile({ firstName, lastName }) {
  const fullName = `${firstName} ${lastName}`;
return <h1>{fullName}</h1>;
}
// ❌ BAD: Unnecessary effect + state
function ProductList({ products }) {
  const [sortedProducts, setSortedProducts] = useState([]);
useEffect(() => {
    const sorted = [...products].sort((a, b) => a.price - b.price);
    setSortedProducts(sorted);
  }, [products]);
  return (
    <ul>
      {sortedProducts.map(product => (
        <li key={product.id}>{product.name} - ${product.price}</li>
      ))}
    </ul>
  );
}
// ✅ GOOD: Calculate during render (useMemo for expensive ops)
function ProductList({ products }) {
  const sortedProducts = useMemo(() => {
    return [...products].sort((a, b) => a.price - b.price);
  }, [products]);
return (
    <ul>
      {sortedProducts.map(product => (
        <li key={product.id}>{product.name} - ${product.price}</li>
      ))}
    </ul>
  );
}
// ❌ BAD: Manual fetching with useEffect
function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
useEffect(() => {
    let cancelled = false;
    async function fetchUser() {
      try {
        setLoading(true);
        const response = await fetch(`/api/users/${userId}`);
        if (!response.ok) throw new Error('Failed to fetch');
        const data = await response.json();
        if (!cancelled) setUser(data);
      } catch (err) {
        if (!cancelled) setError(err.message);
      } finally {
        if (!cancelled) setLoading(false);
      }
    }
    fetchUser();
    return () => { cancelled = true; };
  }, [userId]);
  if (loading) return <Spinner />;
  if (error) return <p>Error: {error}</p>;
  return <div>{user.name}</div>;
}
// ✅ GOOD: Use TanStack Query (React Query)
import { useQuery } from '@tanstack/react-query';
function UserProfile({ userId }) {
  const { data: user, isPending, error } = useQuery({
    queryKey: ['user', userId],
    queryFn: () => fetch(`/api/users/${userId}`).then(r => r.json()),
  });
  if (isPending) return <Spinner />;
  if (error) return <p>Error loading user</p>;
  return <div>{user.name}</div>;
}
// ❌ BAD: Overcomplicating with useEffect
function LikeButton({ postId }) {
  const [liked, setLiked] = useState(false);
useEffect(() => {
    if (liked) {
      fetch(`/api/posts/${postId}/like`, { method: 'POST' });
    } else {
      fetch(`/api/posts/${postId}/unlike`, { method: 'POST' });
    }
  }, [liked, postId]);
  return (
    <button onClick={() => setLiked(!liked)}>
      {liked ? '❤️' : '🤍'}
    </button>
  );
}
// ✅ GOOD: Handle in the event handler
function LikeButton({ postId }) {
  const [liked, setLiked] = useState(false);
const handleLike = async () => {
    const newLiked = !liked;
    setLiked(newLiked);
    await fetch(`/api/posts/${postId}/${newLiked ? 'like' : 'unlike'}`, {
      method: 'POST',
    });
  };
  return (
    <button onClick={handleLike}>
      {liked ? '❤️' : '🤍'}
    </button>
  );
}
function WindowWidth() {
  const [width, setWidth] = useState(window.innerWidth);
useEffect(() => {
    const handleResize = () => setWidth(window.innerWidth);
    window.addEventListener('resize', handleResize);

    // Cleanup is crucial!
    return () => window.removeEventListener('resize', handleResize);
  }, []);
  return <p>Window width: {width}px</p>;
}
function ChatRoom({ roomId }) {
  const [messages, setMessages] = useState([]);
useEffect(() => {
    const connection = createWebSocketConnection(roomId);

    connection.onMessage = (msg) => {
      setMessages(prev => [...prev, msg]);
    };
    connection.connect();
    return () => {
      connection.disconnect(); // Cleanup!
    };
  }, [roomId]);
  return <MessageList messages={messages} />;
}
function ChartComponent({ data }) {
  const chartRef = useRef(null);
  const chartInstance = useRef(null);
useEffect(() => {
    if (chartRef.current && !chartInstance.current) {
      chartInstance.current = new ChartLibrary(chartRef.current, {
        type: 'line',
        data: data,
      });
    }
    return () => {
      chartInstance.current?.destroy();
      chartInstance.current = null;
    };
  }, []);
  return <canvas ref={chartRef} />;
}
function PageTitle({ title }) {
  useEffect(() => {
    document.title = `${title} | My App`;

    return () => {
      document.title = 'My App'; // Reset on unmount
    };
  }, [title]);
return null;
}

Чотири поширені помилки

1. Обчислення значень у ефектах

Обчислюйте під час відображення (або за допомогою useMemo, коли це дороге). Ефекти, які змінюють setState на основі props/state, часто містять заборонені цикли зворотного зв’язку.

2. Дорогі обчислення в ефектах

Залишайте чисті обчислення у функціях відображення/memo, а не після малювання елементів.

3. Завантаження даних як неконтрольований ефект

Завантаження потребує можливості скасування, кешування та належного форматування повідомлень про помилки. Краще використовувати спеціалізовані бібліотеки чи фреймворки з механізмами керування процесом завантаження; якщо використовуєте ефекти, необхідно керувати ситуаціями переривання та конфліктів.

4. Дії користувача в ефектах

Обробники кліків мають знаходитися у функціях подій. Ефекти, які активуються через зміну статусного прапорця внаслідок кліку, створюють непрямість та помилки.

Дерево рішень

Якщо обчислення здійснюється на основі існуючого state/props → використовуйте функцію відображення. Якщо це реакція на клік/натискання „Надіслати“ → використовуйте обробник подій. Якщо це взаємодія з DOM API, мережевим джерелом чи життєвим циклом стороннього віджета → використовуйте ефект.

Коли ефекти є доцільними

Зовнішні слухачі подій

WebSockets

Ініціалізація бібліотек сторонніх розробників

Імперативні API, що не належать до React та які мають чисто завершувати свою роботу

Заключні зауваження

Комплексність менша, потік даних — чіткіший. Використовуйте useEffect лише для синхронізації з системами поза React, а не як універсальний інструмент для кожної операції після відображення контенту.

Режим Strict Mode подвійно запускає ефекти під час розробки, щоб виявити пропущені операції очищення — сприймайте це як перевагу під час роботи з сокетами. Для керування станом сервера використовуйте декларативні бібліотеки даних, щоб компоненти описували залежності, а не створювали власні ефекти отримання даних. Перед додаванням ефектів, які лише копіюють параметри до стану, проведіть аналіз — така практика зазвичай є неправильним розумінням процесу відображення контенту.

Функції очищення мають скасовувати слухачі та таймери; залишені без уваги обробники є типовою причиною попереджень про використання setState після зняття компонента.

Масиви залежностей є частиною контракту — якщо пропустити значення, з’являються застарілі функції; надмірне включення об’єктів та ефектів уповільнює кожне відображення.

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

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

Функції очищення мають видаляти слухачі та таймери; залишені без уваги обробники є класичною причиною попереджень про зміну стану після вивантаження.

Масиви залежностей є частиною контракту — якщо пропустити значення, з’являються застарілі функції; надмірне включення об’єктів та ефектів уповільнює кожне відображення.

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

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

Функції очищення мають скасовувати слухачі та таймери; залишені без уваги обробники є класичною причиною попереджень про зміну стану після вивантаження додатку.

Масиви залежностей є частиною умов контракту — якщо пропустити значення, з’являються старі функції; надмірне включення об’єктів та ефектів уповільнює кожне оновлення додатку.

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

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

Функції очищення мають скасовувати слухачі та таймери; залишені без уваги обробники є класичною причиною попереджень про зміну стану після вивантаження додатку.

Масиви залежностей є частиною контракту — якщо пропустити значення, з’являються застарілі функції; надмірне включення об’єктів та ефектів уповільнює кожне відображення.

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

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

Функції очищення мають видаляти слухачів та таймери; залишені без уваги обробники є класичною причиною попереджень про зміну стану після вивантаження.

Масиви залежностей є частиною контракту — якщо пропустити значення, з’являються застарілі функції; надмірне включення об’єктів та ефектів уповільнює кожне відображення.

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

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

Функції очищення мають скасовувати слухачі та таймери; залишені без уваги обробники є класичною причиною попереджень про зміну стану після вивантаження додатку.

Масиви залежностей є частиною умов контракту — якщо пропустити значення, з’являються старі закриття функцій; надмірне включення об’єктів та ефектів уповільнює кожне рендерування.

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

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

Функції очищення мають скасовувати слухачі та таймери; залишені без уваги обробники є класичною причиною попереджень про зміну стану після вивантаження додатку.

Масиви залежностей є частиною контракту — якщо пропустити значення, з’являються застарілі функції; якщо перелічити занадто багато об’єктів, кожна переробка сторінки стає повільною.

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

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

Функції очищення мають видаляти слухачі та таймери; залишені без уваги обробники є класичною причиною попереджень про зміну стану після вивантаження сторінки.