๐React - ืืืจืื
ืืืืื ืฉืื ืืืจ ืฉืื ืขื ื ืืืื ืฆื ืืคืจืงืื ื ืคืจืืื.

useEffect โ ืืกืืจ ืืื ืืืืืืืืช
ืื ืื useEffect? (ืชืงืฆืืจ ืืืืจ)
useEffect ืืื Hook ืืจืืืงื ืฉืืืคืฉืจ ืืืฆืข ืืฉืคืขืืช ืฆื (Side Effects) ืืชืื ืงืืืคืื ื ืืืช ืคืื ืงืฆืืื ืืืืช โ ืืขืฆื ืื ืขืืืื ืฉืืืืฅ ืืจืื ืืืจ ืขืฆืื: ืื ืืืื (subscriptions), ืฉืืืคืช ื ืชืื ืื (fetch), ืขืืืื ืืฉืืจ ืฉื ื-DOM, ืืืืื ื ืืืจืืขืื, ืืืืืจืื ืืขืื.
- useEffect hook
- ืืงืื ืฉื ื ืืจืืืื ืืื (ืืฉื ื ืืืคืฆืืื ืื)
- ืืืจืืืื ื ืืจืืฉืื โ ืคืื ืงืฆืืืช callback
- ืืืจืืืื ื ืืฉื ื โ ืืขืจื ืชืืืชืื (dependency array)
- ืืจืืจืช ืืืื: ืจืฅ ืืืจื ืื ืจืื ืืืจ (ืืืื ืืจืืฉืื)
- ื-
callback
ืื ืืืื ืืืืืืจ Promise (ืื ืชืกืื ื ืืืชืasync
) - ืื ืืขืจื ืืชืืืชืื ืจืืง
[]
โ ืืจืืฅ ืคืขื ืืืช ืจืง ืืจืื ืืืจ ืืจืืฉืื ื (mount)
ืชืืื ืขื ืืื ืื
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;
ืืืืื ืืื
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;
ืืืืื ืืื
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)
ืืคืงื ืฉืจืฅ ืจืง ืืขืจืืื ืืืืืื
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]);
ืืืืื ืืื: ืืืืืื ืก
ืืืืื ืืื: ืืืืื Resize ืขื ื ืืงืื
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)
ืืขืืืืื ืืจืฉืืืื ืืืืกืืืื ืฉืืืื ืืงืจืื ืืขืืืง:
- React Docs โ useEffect (Reference)
- React Docs โ Synchronizing with Effects (Learn)
- React Docs โ Removing Effect Dependencies
- React Docs โ Lifecycle of Reactive Effects
- React Docs โ Batching of State Updates
- React Docs โ StrictMode (ืืชื ืืืืช ืืคืืชืื)
- MDN โ AbortController (ืืืืื ืืงืฉืืช fetch)