React logo

useEffect โ€“ ื”ืกื‘ืจ ืžืœื ื•ื“ื•ื’ืžืื•ืช

ืžื” ื–ื” useEffect? (ืชืงืฆื™ืจ ืžื”ื™ืจ)

useEffect ื”ื•ื Hook ื‘ืจื™ืืงื˜ ืฉืžืืคืฉืจ ืœื‘ืฆืข ื”ืฉืคืขื•ืช ืฆื“ (Side Effects) ื‘ืชื•ืš ืงื•ืžืคื•ื ื ื˜ื•ืช ืคื•ื ืงืฆื™ื•ื ืœื™ื•ืช โ€” ื‘ืขืฆื ื›ืœ ืขื‘ื•ื“ื” ืฉืžื—ื•ืฅ ืœืจื™ื ื“ื•ืจ ืขืฆืžื•: ืžื ื•ื™ื™ื (subscriptions), ืฉืœื™ืคืช ื ืชื•ื ื™ื (fetch), ืขื“ื›ื•ืŸ ื™ืฉื™ืจ ืฉืœ ื”-DOM, ืžืื–ื™ื ื™ ืื™ืจื•ืขื™ื, ื˜ื™ื™ืžืจื™ื ื•ืขื•ื“.

  • useEffect hook
  • ืžืงื‘ืœ ืฉื ื™ ืืจื’ื•ืžื ื˜ื™ื (ื”ืฉื ื™ ืื•ืคืฆื™ื•ื ืœื™)
  • ื”ืืจื’ื•ืžื ื˜ ื”ืจืืฉื•ืŸ โ€“ ืคื•ื ืงืฆื™ื™ืช callback
  • ื”ืืจื’ื•ืžื ื˜ ื”ืฉื ื™ โ€“ ืžืขืจืš ืชืœื•ืชื™ื (dependency array)
  • ื‘ืจื™ืจืช ืžื—ื“ืœ: ืจืฅ ืื—ืจื™ ื›ืœ ืจื™ื ื“ื•ืจ (ื›ื•ืœืœ ื”ืจืืฉื•ืŸ)
  • ื”-callback ืœื ื™ื›ื•ืœ ืœื”ื—ื–ื™ืจ Promise (ืืœ ืชืกืžื ื• ืื•ืชื• async)
  • ืื ืžืขืจืš ื”ืชืœื•ืชื™ื ืจื™ืง [] โ€“ ื™ืจื•ืฅ ืคืขื ืื—ืช ืจืง ื‘ืจื™ื ื“ื•ืจ ื”ืจืืฉื•ื ื™ (mount)

ืชื•ื›ืŸ ืขื ื™ื™ื ื™ื

  1. ื”ืงื“ืžื”
  2. UseEffectBasics โ€“ ื“ื•ื’ืžื” ื‘ืกื™ืกื™ืช
  3. Multiple Effects โ€“ ื›ืžื” ืืคืงื˜ื™ื ื ืคืจื“ื™ื
  4. Conditional โ€“ ืชื ืื™ ื‘ืœื™ ืœืฉื‘ื•ืจ Hooks
  5. Cleanup โ€“ ืžื ื™ืขืช ื“ืœื™ืคื•ืช/ืขื“ื›ื•ื ื™ื
  6. ื˜ื™ืคื™ื ื•ื“ื’ืฉื™ื ื—ืฉื•ื‘ื™ื

1)ืžื” ื–ื”:

useEffect ืžืจื™ืฅ ืงื•ื“ ืื—ืจื™ ื”ืจื™ื ื“ื•ืจ. ื ื™ืชืŸ ืœื”ื—ื–ื™ืจ ืคื•ื ืงืฆื™ื” ืžืชื•ืš ื”ืืคืงื˜ ืœืฆืจื›ื™ ื ื™ืงื•ื™(Cleanup) ืฉืชื•ืคืขืœ ืœืคื ื™ ื”ืจื™ืฆื” ื”ื‘ืื” ืฉืœ ื”ืืคืงื˜ ืื• ื‘ืขืช ื”ืกืจืช ื”ืงื•ืžืคื•ื ื ื˜ื” ืžื”ืžืกืš (unmount).

useEffect(() => {
  // ืงื•ื“ ืฉื™ืจื•ืฅ ืื—ืจื™ ื”ืจื™ื ื“ื•ืจ
  return () => {
    // ื ื™ืงื•ื™: ื™ืจื•ืฅ ืœืคื ื™ ืืคืงื˜ ื”ื‘ื ืื• ื‘ืขืช unmount
  };
}, [/* ืชืœื•ืชื™ื */]);
  • ื‘ืœื™ ืžืขืจืš ืชืœื•ืช โ€“ ืจืฅ ืื—ืจื™ ื›ืœ ืจื™ื ื“ื•ืจ (ื›ื•ืœืœ ื”ืจืืฉื•ืŸ).
  • ืขื [] โ€“ ืจืฅ ืคืขื ืื—ืช ื‘-mount; ื”-cleanup ื™ืจื•ืฅ ื‘-unmount.
  • ืขื [a, b] โ€“ ืจืฅ ื‘-mount ื•ื‘ื›ืœ ืฉื™ื ื•ื™ ืฉืœ ืื—ื“ ื”ืชืœื•ืชื™ื.

ืœืžื” ืœืคืขืžื™ื ื ื•ืฆืจืช ืœื•ืœืื” ืื™ื ืกื•ืคื™ืช?

ืงืจื™ืื” ืœ-setState ื‘ื–ืžืŸ ืจื™ื ื“ื•ืจ (ื‘ื’ื•ืฃ ื”ืงื•ืžืคื•ื ื ื˜ื”) ืื• ื‘ืชื•ืš ืืคืงื˜ ืฉืชืœื•ื™ ื‘ืื•ืชื• ืกื˜ื™ื™ื˜ ื‘ืœื™ ืชื ืื™ ืชื’ืจื•ื ืœืจื™ื ื“ื•ืจ ื ื•ืกืฃ โ€” ื•ืฉื•ื‘ setState โ€” ื•ื—ื•ื–ืจ ื—ืœื™ืœื”. ืœืขื•ืœื ืœื ืžื‘ืฆืขื™ื setState ื™ืฉื™ืจื•ืช ื‘ื’ื•ืฃ ื”ืจื›ื™ื‘.

โŒ ื“ื•ื’ืžื” ื‘ืขื™ื™ืชื™ืช

const Bad = () => {
  const [value, setValue] = useState(0);
  const sayHello = () => {
    console.log('hello world');
    setValue(value + 1); // ื™ื’ืจื•ื ืœืจื™ื ื“ื•ืจ ื ื•ืกืฃ ืžื™ื“
  };
  sayHello(); // ืจืฅ ื‘ื›ืœ ืจื™ื ื“ื•ืจ!
  return <h1>value: {value}</h1>;
};

โœ… ื”ืชื™ืงื•ืŸ (ื”ืขื‘ืจื” ืœ-useEffect ื—ื“-ืคืขืžื™)

const Good = () => {
  const [value, setValue] = useState(0);
  useEffect(() => {
    console.log('hello world');
    setValue(v => v + 1); // ื™ืจื•ืฅ ืคืขื ืื—ืช ื‘ืœื‘ื“
  }, []);
  return (
    <div>
      <h1>value: {value}</h1>
      <button className="btn" onClick={() => setValue(v => v + 1)}>click me</button>
    </div>
  );
};

2) UseEffectBasics โ€“ ื“ื•ื’ืžื” ื‘ืกื™ืกื™ืช

ื‘ื“ื•ื’ืžื” ื”ื‘ืื” ื ืจืื” ืื™ืš ืงื•ื“ ืฉื ืžืฆื ืžื—ื•ืฅ ืœ-useEffect ืจืฅ ื‘ื›ืœ ืจื™ื ื“ื•ืจ, ืœืขื•ืžืช ืงื•ื“ ืฉื ืžืฆื ื‘ืชื•ืšuseEffect ืขื [] ืฉืจืฅ ืคืขื ืื—ืช ื‘ืœื‘ื“ (Mount).

import { useState, useEffect } from 'react';

const UseEffectBasics = () => {
  const [value, setValue] = useState(0);
  const sayHello = () => {
    console.log('hello there');
  };

  sayHello();

  // useEffect(() => {
  //   console.log('hello from useEffect');
  // });

  useEffect(() => {
    console.log('hello from useEffect');
  }, []);
  return (
    <div>
      <h1>value : {value}</h1>
      <button className='btn' onClick={() => setValue(value + 1)}>
        click me
      </button>
    </div>
  );
};
export default UseEffectBasics;
ื“ื•ื’ืžื” ื—ื™ื”
value: 0

3) Multiple Effects โ€“ ื›ืžื” ืืคืงื˜ื™ื ื ืคืจื“ื™ื

ื ื™ืชืŸ ืœื”ื’ื“ื™ืจ ื›ืžื” useEffect ื‘ืื•ืชื• ืจื›ื™ื‘, ืœื›ืœ ืื—ื“ ืชืœื•ืชื™ื ืžืฉืœื• ื•ืœื•ื’ื™ืงื” ืžืฉืœื•. ื–ื” ืžืกื™ื™ืข ืœื”ืคืจื“ืช ืื—ืจื™ื•ืช (Separation of Concerns).

// import Starter from './tutorial/02-useEffect/starter/03-multiple-effects.jsx';

import { useState, useEffect } from 'react';

const MultipleEffects = () => {
  const [value, setValue] = useState(0);
  const [secondValue, setSecondValue] = useState(0);

  useEffect(() => {
    console.log('hello from first useEffect');
  }, [value]);

  useEffect(() => {
    console.log('hello from second useEffect');
  }, [secondValue]);
  return (
    <div>
      <h1>value : {value}</h1>
      <button className='btn' onClick={() => setValue(value + 1)}>
        value
      </button>
      <h1>second value : {secondValue}</h1>
      <button className='btn' onClick={() => setSecondValue(secondValue + 1)}>
        second value
      </button>
    </div>
  );
};
export default MultipleEffects;
ื“ื•ื’ืžื” ื—ื™ื”
value: 0
second value: 0

4) React.useEffect โ€“ Conditional

ืืกื•ืจ ืœืขื˜ื•ืฃ useEffect ืขืฆืžื• ื‘ืชื•ืš if/for/while/try โ€” ื–ื” ืฉื•ื‘ืจ ืืช ืกื“ืจ ื”-Hooks. ื‘ืžืงื•ื ื–ืืช, ืฉืžื™ื ืชื ืื™ ื‘ืชื•ืš ื”ืืคืงื˜ ืื• ืฉื•ืœื˜ื™ื ื‘ื”ืจืฆื” ื“ืจืš ืžืขืจืš ื”ืชืœื•ืชื™ื.

โœ… ืชื ืื™ ื‘ืชื•ืš ื”ืืคืงื˜

useEffect(() => {
  if (!shouldRun) return; // ื™ืฆื™ืื” ืžื•ืงื“ืžืช
  // ืœื•ื’ื™ืงื” ืฉืจืฆื” ืจืง ื›ืฉ-shouldRun=true
}, [shouldRun]);

โœ… ืจื™ืฆื” ืจืง ื›ืืฉืจ count ื–ื•ื’ื™

useEffect(() => {
  if (count % 2 !== 0) return;
  console.log('even:', count);
}, [count]);

โœ… ื“ื™ืœื•ื’ ืขืœ ื”ืจื™ืฆื” ื”ืจืืฉื•ื ื” (skip first run)

const first = useRef(true);
useEffect(() => {
  if (first.current) { first.current = false; return; }
  // ื™ืจื•ืฅ ืจืง ืžื”ืจื™ื ื“ื•ืจ ื”ืฉื ื™ ื•ื”ืœืื”
}, [value]);
ืขื“ื›ื•ืŸ ื›ื•ืชืจืช ื˜ืื‘ (title)
0
ืืคืงื˜ ืฉืจืฅ ืจืง ื‘ืขืจื›ื™ื ื–ื•ื’ื™ื™ื
0 (ืจืฅ ืจืง ื›ืฉื–ื•ื’ื™)

5) ื ื™ืงื•ื™ (Cleanup) โ€“ ืœืžื ื•ืข ืขื“ื›ื•ื ื™ื/ื“ืœื™ืคื•ืช

ื”-cleanup ืžื—ื–ื™ืจ ืืช ื”ืžืขืจื›ืช ืœืžืฆื‘ ื ืงื™ ื‘ื™ืŸ ืจื™ืฆื•ืช ื”ืืคืงื˜/ื‘ืขืช unmount: ืžืกื™ืจื™ื ืžืื–ื™ื ื™ื, ืžื‘ื˜ืœื™ื ื˜ื™ื™ืžืจื™ื, ืกื•ื’ืจื™ื ืžื ื•ื™ื™ื ื•ืžื‘ื˜ืœื™ื ื‘ืงืฉื•ืช ืจืฉืช ืคืขื™ืœื•ืช ื›ื“ื™ ืœืžื ื•ืข setState ืื—ืจื™ ืฉื”ืจื›ื™ื‘ ื”ื•ืกืจ.

๐ŸŸข ืžืื–ื™ืŸ ืื™ืจื•ืขื™ื

useEffect(() => {
  const onResize = () => console.log('resizing...');
  window.addEventListener('resize', onResize);
  return () => window.removeEventListener('resize', onResize);
}, []);

๐ŸŸข ื˜ื™ื™ืžืจ / ืื™ื ื˜ืจื•ื•ืœ

useEffect(() => {
  const id = setInterval(() => console.log('tick'), 1000);
  return () => clearInterval(id);
}, []);

๐ŸŸข ื‘ืงืฉืช API ืขื ื‘ื™ื˜ื•ืœ (AbortController)

useEffect(() => {
  const ac = new AbortController();
  (async () => {
    try {
      const res = await fetch(url, { signal: ac.signal });
      const json = await res.json();
      setData(json);
    } catch (err) {
      if (err.name !== 'AbortError') console.error(err);
    }
  })();
  return () => ac.abort();
}, [url]);

๐ŸŸข ื“ื™ื‘ืื•ื ืก (Debounce) ืœืฉื“ื” ืงืœื˜

const [input, setInput] = useState('');
const [searchTerm, setSearchTerm] = useState('');
useEffect(() => {
  const id = setTimeout(() => setSearchTerm(input), 300);
  return () => clearTimeout(id);
}, [input]);
ื“ื•ื’ืžื” ื—ื™ื”: ื“ื™ื‘ืื•ื ืก
searchTerm:
ื“ื•ื’ืžื” ื—ื™ื”: ืžืื–ื™ืŸ Resize ืขื ื ื™ืงื•ื™
window.innerWidth = 0px

6) ื˜ื™ืคื™ื ื•ื“ื’ืฉื™ื

  • ืืœ ืชื›ืชื‘ื• async ื™ืฉื™ืจื•ืช ืขืœ ืคื•ื ืงืฆื™ื™ืช useEffect; ื›ืชื‘ื• ืคื•ื ืงืฆื™ื” ืคื ื™ืžื™ืช ืืกื™ื ื›ืจื•ื ื™ืช ื•ื”ืคืขื™ืœื• ืื•ืชื” ื‘ืชื•ืš ื”ืืคืงื˜.
  • ื”ืขื“ื™ืคื• ืขื“ื›ื•ืŸ ืคื•ื ืงืฆื™ื•ื ืœื™: setValue(v => v + 1) โ€“ ืžืคื—ื™ืช ืชืœื•ืชื™ื ื•ืฉื•ืžืจ ืขืœ ืขืจืš ืขื“ื›ื ื™ ื‘ืชื•ืจื™ื.
  • ื›ืœ ืขืจืš/ืคื•ื ืงืฆื™ื” ืฉื ืขืฉื” ื‘ื• ืฉื™ืžื•ืฉ ืคื ื™ืžื™ ืฆืจื™ืš ืœื”ื•ืคื™ืข ื‘ืžืขืจืš ื”ืชืœื•ืชื™ื (ืื• ืœื”ื™ื•ืช ืžืžื•ืžื•ืื– ื‘-useCallback/useMemo).
  • ื‘ืžืฆื‘ ืคื™ืชื•ื— ืขื Strict Mode ื™ื™ืชื›ืŸ ืฉืชืจื•ืฅ ืกื™ืžื•ืœืฆื™ื™ืช mount/unmount ื ื•ืกืคืช ื›ื“ื™ ืœื—ืฉื•ืฃ ื‘ืขื™ื•ืช; ื‘ืคืจื•ื“ืงืฉืŸ ื–ื” ืœื ืงื•ืจื”.
  • ื ื™ืชืŸ ืœืคืจืง ืœื•ื’ื™ืงื” ืœืžืกืคืจ useEffect ื ืคืจื“ื™ื ื‘ืžืงื•ื ืœืื—ื“ ื”ื›ื•ืœ ืœืืคืงื˜ ืื—ื“ ื’ื“ื•ืœ โ€“ ื–ื” ืงืจื™ื ื•ื‘ื˜ื•ื— ื™ื•ืชืจ.

๐Ÿš€ ื”ืœืื”

ืขื›ืฉื™ื• ื›ืฉื™ืฉ ืœืš ืชื‘ื ื™ืช ืžืœืื” ืœ-useEffect ืขื ื”ืกื‘ืจื™ื, ื“ื•ื’ืžืื•ืช ื—ื™ื•ืช ื•ืงื˜ืขื™ ืงื•ื“ โ€“ ืชื•ื›ืœ/ื™ ืœืฉืœื‘ ืื•ืชื” ื‘ืคืจื•ื™ืงื˜ื™ื ืืžื™ืชื™ื™ื: ื˜ืขื™ื ืช ื ืชื•ื ื™ื, ืฉืœื™ื˜ื” ื‘ืžื—ื–ื•ืจ ื—ื™ื™ื, ื•ื ื™ื”ื•ืœ ื‘ื™ืฆื•ืขื™ื. ืืคืฉืจ ืœื”ืขืชื™ืง ื›ืœ ืžืงื˜ืข ื•ืœื‘ื ื•ืช ืกื‘ื™ื‘ื• ืฉื™ืขื•ืจ/ืชืจื’ื™ืœ.

8) useEffect + Fetch Data โ€” ืœืžื” ื•ืื™ืš?

ืœืžื” useEffect? ื›ื™ ืฉืœื™ืคืช ื ืชื•ื ื™ื ืžื”ืื™ื ื˜ืจื ื˜ (fetch) ื”ื™ื "ื”ืฉืคืขืช ืฆื“" โ€” ืืกื•ืจ ืœื”ืจื™ืฅ ืื•ืชื” ื‘ืชื•ืš ืคื•ื ืงืฆื™ื™ืช ื”ืจื™ื ื“ื•ืจ. useEffect ืจืฅ ืื—ืจื™ ื”ืจื™ื ื“ื•ืจ, ืžืืคืฉืจ ืœื ื• ืœืฉืœื•ื˜ ื‘ืชื–ืžื•ืŸ (ืคืขื ืื—ืช ืขื []) ื•ืœื”ื—ื–ื™ืจ ืคื•ื ืงืฆื™ื™ืช ื ื™ืงื•ื™.

  • [] ื’ื•ืจื ืœืืคืงื˜ ืœืจื•ืฅ ืคืขื ืื—ืช ื‘ืžื•ื ื˜ (ืขืœื™ื™ื” ืจืืฉื•ื ื™ืช ืฉืœ ื”ืงื•ืžืคื•ื ื ื˜ื”).
  • AbortController ืžื‘ื˜ืœ ืืช ื”ื‘ืงืฉื” ืื ื”ืงื•ืžืคื•ื ื ื˜ื” ืžืชืคืจืงืช โ€” ืื™ืŸ ื“ืœื™ืคื•ืช.
  • setUsers(data) ืžืขื“ื›ืŸ state ื•ื’ื•ืจื ืœึพre-render ืฉืžืฆื™ื’ ืืช ื”ืจืฉื™ืžื”.
  • ื‘ึพStrict Mode (ืคื™ืชื•ื—) ื”ืืคืงื˜ ื™ื›ื•ืœ ืœื”ืชืจื•ืฅ ืคืขืžื™ื™ื โ€” ื”ื ื™ืงื•ื™ ืžื’ืŸ ืขืœื™ื ื•.
// ืžืกืžืŸ ืœืงื•ืžืคื•ื ื ื˜ื” ืฉื”ื™ื ืงื•ืžืคื•ื ื ื˜ืช-ืœืงื•ื— (Client Component) ื‘ืกื‘ื™ื‘ืช Next.js (App Router)
"use client";

// ืžื™ื™ื‘ืื™ื ืฉื ื™ Hooks ืž-React: useState (ื ื™ื”ื•ืœ ืžืฆื‘) ื•-useEffect (ื”ืจืฆืช ืืคืงื˜ื™ื ืื—ืจื™ ืจื™ื ื“ื•ืจ)
import { useState, useEffect } from "react";

// ื›ืชื•ื‘ืช ื”-API ืฉืžืžื ื” ื ืฉืœื•ืฃ ืืช ืจืฉื™ืžืช ืžืฉืชืžืฉื™ GitHub (ืžืขืจืš ืฉืœ ืื•ื‘ื™ื™ืงื˜ื™ื)
const URL = "https://api.github.com/users";

// ื”ื’ื“ืจืช ืงื•ืžืคื•ื ื ื˜ืช React ืคื•ื ืงืฆื™ื•ื ืœื™ืช ื‘ืฉื FetchData ื•ื™ื™ืฆื•ื ื›ื‘ืจื™ืจืช ืžื—ื“ืœ
export default function FetchData() {
  // users: ืžืฆื‘ ืฉืžื—ื–ื™ืง ืืช ื ืชื•ื ื™ ื”ืžืฉืชืžืฉื™ื (ื‘ืจื™ืจืช ืžื—ื“ืœ: ืžืขืจืš ืจื™ืง)
  // setUsers: ืคื•ื ืงืฆื™ื” ืœืขื“ื›ื•ืŸ users
  const [users, setUsers] = useState([]);     // default []

  // loading: ื“ื’ืœ ื˜ืขื™ื ื” โ€” true ืขื“ ืฉื”ื‘ืืช ื”ื ืชื•ื ื™ื ืžืกืชื™ื™ืžืช
  // setLoading: ืขื“ื›ื•ืŸ ื“ื’ืœ ื”ื˜ืขื™ื ื”
  const [loading, setLoading] = useState(true);

  // error: ืžื—ืจื•ื–ืช ืฉื’ื™ืื” ืœืชืฆื•ื’ื” ืื ืžืฉื”ื• ื ื›ืฉืœ
  // setError: ืขื“ื›ื•ืŸ ื”ื•ื“ืขืช ื”ืฉื’ื™ืื”
  const [error, setError] = useState("");

  // useEffect ื™ืจื•ืฅ ืื—ืจื™ ื”ืจื™ื ื“ื•ืจ ื”ืจืืฉื•ืŸ ื‘ืœื‘ื“ (ื‘ื–ื›ื•ืช []), ื›ื“ื™ ืœื‘ืฆืข ืืช ื”-fetch ืžื”-API
  useEffect(() => {
    // AbortController ืžืืคืฉืจ ืœื‘ื˜ืœ ืืช ื‘ืงืฉืช ื”-fetch ืื ื”ืงื•ืžืคื•ื ื ื˜ื” ืžืชืคืจืงืช
    const ctrl = new AbortController();

    // ืคื•ื ืงืฆื™ื” ืืกื™ื ื›ืจื•ื ื™ืช ืฉืžื‘ืฆืขืช ืืช ืฉืœื™ืคืช ื”ื ืชื•ื ื™ื
    const fetchData = async () => {
      try {
        // fetch ืœื›ืชื•ื‘ืช ื”-API; ืžืขื‘ื™ืจื™ื signal ื›ื“ื™ ืฉื ื•ื›ืœ ืœื‘ื˜ืœ ืื ืฆืจื™ืš
        const res = await fetch(URL, { signal: ctrl.signal });

        // ืื ื”ืกื˜ื˜ื•ืก ืœื ืชืงื™ืŸ (ืœื 200-299) โ€” ื ื–ืจื•ืง ืฉื’ื™ืื” ื™ื“ื ื™ืช
        if (!res.ok) throw new Error(`HTTP ${res.status}`);

        // ืžืžื™ืจื™ื ืืช ื”-Response ืœ-JSON โ€” ืฆืคื•ื™ ืœืงื‘ืœ ืžืขืจืš ืฉืœ ืžืฉืชืžืฉื™ื
        const data = await res.json();

        // ืฉื•ืžืจื™ื ืืช ื”ืชื•ืฆืื” ื‘-state; ื–ื” ื™ื’ืจื•ื ืœืจื™ื ื“ื•ืจ ืžื—ื“ืฉ ืขื ื”ื ืชื•ื ื™ื
        setUsers(data); // set users equal to result
      } catch (err) {
        // ืื ื–ื• ืœื ืฉื’ื™ืืช ื‘ื™ื˜ื•ืœ (AbortError) โ€” ื ืฉืžื•ืจ ืืช ื”ื•ื“ืขืช ื”ืฉื’ื™ืื” ืœื”ืฆื’ื”
        if (err.name !== "AbortError") setError(String(err.message || err));
      } finally {
        // ื‘ื›ืœ ืžืงืจื” (ื”ืฆืœื—ื”/ื›ื™ืฉืœื•ืŸ) โ€” ื ื›ื‘ื” ืืช ืžืกืš ื”ื˜ืขื™ื ื”
        setLoading(false);
      }
    };

    // ืžืคืขื™ืœื™ื ืืช ืฉืœื™ืคืช ื”ื ืชื•ื ื™ื ืคืขื ืื—ืช ื‘ืจื™ื ื“ื•ืจ ื”ืจืืฉื•ื ื™
    fetchData();          // runs only on initial render

    // ืคื•ื ืงืฆื™ื™ืช ื ื™ืงื•ื™: ืื ื”ืงื•ืžืคื•ื ื ื˜ื” ืžืชื ืชืงืช โ€” ื ื‘ื˜ืœ ืืช ื”ื‘ืงืฉื” ื›ื“ื™ ืœืžื ื•ืข ื“ืœื™ืคื•ืช
    return () => ctrl.abort();
  }, []); // ืžืขืจืš ืชืœื•ืช ืจื™ืง โ†’ ืžืจื™ืฅ ืืช ื”ืืคืงื˜ ืคืขื ืื—ืช ื‘ืœื‘ื“

  // ืชืฆื•ื’ื” ื‘ืžื”ืœืš ื˜ืขื™ื ื”: ื›ื•ืชืจืช + ื˜ืงืกื˜ "loadingโ€ฆ"
  if (loading) {
    return (
      <section>
        <h3>github users</h3>
        <p>loadingโ€ฆ</p>
      </section>
    );
  }

  // ืชืฆื•ื’ืช ืฉื’ื™ืื” ื‘ืžืงืจื” ืฉืœ ื›ืฉืœ: ื›ื•ืชืจืช + ื”ื•ื“ืขืช ื”ืฉื’ื™ืื” ื‘ืื“ื•ื
  if (error) {
    return (
      <section>
        <h3>github users</h3>
        <p style={{ color: "tomato" }}>error: {error}</p>
      </section>
    );
  }

  // ืชืฆื•ื’ืช ื‘ืจื™ืจืช ื”ืžื—ื“ืœ โ€” ืื—ืจื™ ืฉื”ื ืชื•ื ื™ื ื ื˜ืขื ื• ื‘ื”ืฆืœื—ื”
  return (
    <section>
      {/* ื›ื•ืชืจืช ื”ืกืงืฉืŸ */}
      <h3>github users</h3>

      {/* ืจืฉื™ืžืช ื”ืžืฉืชืžืฉื™ื; listStyle:none ื›ื“ื™ ืœื”ืกืชื™ืจ ื‘ื•ืœื˜ื™ื; padding:0 ืœืื™ืคื•ืก */}
      <ul className="users" style={{ listStyle: "none", padding: 0 }}>
        {/* ืžืขื‘ืจ ืขืœ ื”ืžืขืจืš users ื•ื”ื—ื–ืจืช <li> ืœื›ืœ ืžืฉืชืžืฉ */}
        {users.map((user) => {
          // ืคื™ืจื•ืง ืžืืคื™ื™ื ื™ ืžืฉืชืžืฉ: id (ืžืคืชื— ื™ื™ื—ื•ื“ื™), login (ืฉื ืžืฉืชืžืฉ),
          // avatar_url (ืชืžื•ื ืช ืคืจื•ืคื™ืœ), html_url (ืงื™ืฉื•ืจ ืœืขืžื•ื“ ื”ืคืจื•ืคื™ืœ)
          const { id, login, avatar_url, html_url } = user;

          // ืืœืžื ื˜ ืจืฉื™ืžื” ืœื›ืœ ืžืฉืชืžืฉ
          return (
            <li
              key={id} // React ืžืฉืชืžืฉ ื‘ืžืคืชื— ืœื™ืขื™ืœื•ืช ื‘ืจื™ื ื“ื•ืจ ืจืฉื™ืžื•ืช
              style={{ display: "flex", gap: 12, margin: "12px 0" }} // ืคืจื™ืกื” ืื•ืคืงื™ืช ื•ืจื™ื•ื•ื—
            >
              {/* ืชืžื•ื ืช ืื•ื•ื˜ืืจ; ื‘-Next.js ืืคืฉืจ ืœื”ื—ืœื™ืฃ ืœ-next/image ืขื ื”ื’ื“ืจืช ื“ื•ืžื™ื™ืŸ */}
              <img
                src={avatar_url} // ื›ืชื•ื‘ืช ืœืชืžื•ื ืช ื”ืคืจื•ืคื™ืœ
                alt={login}      // ื˜ืงืกื˜ ื—ืœื•ืคื™ ื ื’ื™ืฉ
                width={48}
                height={48}
                style={{ borderRadius: 8 }} // ืคื™ื ื•ืช ืžืขื•ื’ืœื•ืช
              />

              {/* ืคืจื˜ื™ ื”ืžืฉืชืžืฉ: ืฉื + ืงื™ืฉื•ืจ ืœืคืจื•ืคื™ืœ */}
              <div>
                {/* ืฉื ื”ืžืฉืชืžืฉ; margin:0 ื›ื“ื™ ืœืฉืžื•ืจ ืขืœ ืงื•ืžืคืงื˜ื™ื•ืช */}
                <h5 style={{ margin: 0 }}>{login}</h5>

                {/* ืงื™ืฉื•ืจ ืœืคืจื•ืคื™ืœ GitHub; ื ืคืชื— ื‘ื˜ืื‘ ื—ื“ืฉ; rel ืœืฉื™ืงื•ืœื™ ืื‘ื˜ื—ื” */}
                <a href={html_url} target="_blank" rel="noreferrer">
                  profile
                </a>
              </div>
            </li>
          );
        })}
      </ul>
    </section>
  );
}

ืชื•ืฆืื”

useEffect ืžืจื™ืฅ ืืช ื”ึพfetch ืคืขื ืื—ืช ื‘ืžื•ื ื˜ (ื‘ื–ื›ื•ืช []), ืฉื•ืžืจ ืชื•ืฆืื” ื‘ึพstate ื•ืžืจื ื“ืจ ืžื—ื“ืฉ. ื™ืฉ ื’ื ื ื™ืงื•ื™ ืขื AbortController.

ื“ื•ื’ืžื” ื—ื™ื”

ื˜ื•ืขืŸโ€ฆ

๐Ÿ“š ืžืงื•ืจื•ืช ืžื•ืžืœืฆื™ื (Docs)

ื”ืขืžื•ื“ื™ื ื”ืจืฉืžื™ื™ื ื•ื”ืžืกื›ืžื™ื ืฉื›ื“ืื™ ืœืงืจื•ื ืœืขื•ืžืง: