React / Custom Hook

Custom Hook

React-н built-in hook-ууд (useState, useEffect, useRef гэх мэт) маш хүчирхэг — гэвч заримдаа нэг логикийг олон component-д давтан бичих хэрэгтэй болдог. Custom Hook бол тэрхүү давтагдах логикийг нэг дахин ашиглагдах функцэд хураадаг арга юм. use үгээр эхэлдэг энгийн функц — React-н дүрмийг дагаж чадаад байвал та өөрийн hook бичиж болно.

Custom Hook гэж юу вэ?

Custom Hook бол use үгээр эхэлдэг, бусад hook-уудыг дотроо ашиглаж болдог функц юм. Component биш — JSX буцаадаггүй, зөвхөн утга, функц буцаана:

jsx
// hooks/useCounter.js
import { useState } from "react";

function useCounter(initialValue = 0, step = 1) {
  const [count, setCount] = useState(initialValue);

  function increment() {
    setCount((c) => c + step);
  }
  function decrement() {
    setCount((c) => c - step);
  }
  function reset() {
    setCount(initialValue);
  }

  return { count, increment, decrement, reset };
}

export default useCounter;
jsx
// Дурын component-д ашиглана
import useCounter from "./hooks/useCounter";

function ScoreBoard() {
  const score = useCounter(0, 10);
  const lives = useCounter(3, 1);

  return (
    <div>
      <p>Оноо: {score.count}</p>
      <button onClick={score.increment}>+10</button>
      <button onClick={score.reset}>Дахилт</button>

      <p>Амь: {lives.count}</p>
      <button onClick={lives.decrement}>-1</button>
    </div>
  );
}

useFetch — өгөгдөл татах hook

API-аас өгөгдөл татах логик олон component-д давтагддаг — custom hook болгоход хамгийн тохиромжтой тохиолдол:

jsx
// hooks/useFetch.js
import { useState, useEffect } from "react";

function useFetch(url) {
  const [data, setData] = useState(null);
  const [isLoading, setIsLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    if (!url) return;

    let isCancelled = false;

    async function fetchData() {
      try {
        setIsLoading(true);
        setError(null);

        const response = await fetch(url);
        if (!response.ok) throw new Error(`HTTP алдаа: ${response.status}`);

        const json = await response.json();
        if (!isCancelled) setData(json);
      } catch (err) {
        if (!isCancelled) setError(err.message);
      } finally {
        if (!isCancelled) setIsLoading(false);
      }
    }

    fetchData();

    return () => {
      isCancelled = true;
    };
  }, [url]);

  return { data, isLoading, error };
}

export default useFetch;
jsx
// Хэрэглэх — логик нуугдаж, component цэвэр болно
import useFetch from "./hooks/useFetch";

function CourseList() {
  const { data: courses, isLoading, error } = useFetch("/api/courses");

  if (isLoading) return <p>Ачааллаж байна...</p>;
  if (error) return <p>Алдаа: {error}</p>;

  return (
    <ul>
      {courses.map((c) => (
        <li key={c.id}>{c.title}</li>
      ))}
    </ul>
  );
}

useLocalStorage — тогтвортой хадгалах

Хөтчийн localStorage-тай ажиллах логикийг hook болгосон жишээ:

jsx
// hooks/useLocalStorage.js
import { useState } from "react";

function useLocalStorage(key, initialValue) {
  const [stored, setStored] = useState(() => {
    try {
      const item = localStorage.getItem(key);
      return item ? JSON.parse(item) : initialValue;
    } catch {
      return initialValue;
    }
  });

  function setValue(value) {
    try {
      const valueToStore = value instanceof Function ? value(stored) : value;
      setStored(valueToStore);
      localStorage.setItem(key, JSON.stringify(valueToStore));
    } catch (err) {
      console.error("localStorage алдаа:", err);
    }
  }

  return [stored, setValue];
}

export default useLocalStorage;
jsx
import useLocalStorage from "./hooks/useLocalStorage";

function ThemeToggle() {
  const [theme, setTheme] = useLocalStorage("theme", "dark");

  return (
    <button onClick={() => setTheme((t) => (t === "dark" ? "light" : "dark"))}>
      {theme === "dark" ? "☀️ Цайвар" : "🌙 Харанхуй"}
    </button>
  );
}

useDebounce — хайлтыг оновчлох

Хэрэглэгч бичиж байхад API-д хэт олон хүсэлт илгээхээс сэргийлэх debounce hook:

jsx
// hooks/useDebounce.js
import { useState, useEffect } from "react";

function useDebounce(value, delay = 300) {
  const [debouncedValue, setDebouncedValue] = useState(value);

  useEffect(() => {
    const timer = setTimeout(() => {
      setDebouncedValue(value);
    }, delay);

    return () => clearTimeout(timer);
  }, [value, delay]);

  return debouncedValue;
}

export default useDebounce;
jsx
import { useState } from "react";
import useDebounce from "./hooks/useDebounce";
import useFetch from "./hooks/useFetch";

function SearchPage() {
  const [query, setQuery] = useState("");
  const debouncedQuery = useDebounce(query, 400);

  const { data, isLoading } = useFetch(
    debouncedQuery ? `/api/search?q=${debouncedQuery}` : null,
  );

  return (
    <div>
      <input
        value={query}
        onChange={(e) => setQuery(e.target.value)}
        placeholder="Хайх..."
      />
      {isLoading && <p>Хайж байна...</p>}
      {data?.map((item) => (
        <p key={item.id}>{item.title}</p>
      ))}
    </div>
  );
}

Custom hook-уудыг хооронд нь хослуулж болдог — useDebounce + useFetch хамт ажиллаж байгааг анзаарна уу. Энэ бол React-н хамгийн хүчирхэг боломжуудын нэг: жижиг, нэг ажил хийдэг hook-уудыг найруулан том зүйл бүтээх.

Дараагийн хичээлд:

React.memo ашиглан component-г дахин рендер болохоос хамгаалах аргыг судална. Хэзээ memo хэрэгтэй, хэзээ хэрэггүй, буруу хэрэглэлийн нийтлэг алдаануудтай танилцана.