Bỏ qua để đến nội dung

React Hooks

Hooks cho phép sử dụng state và các tính năng React trong function components.

Quản lý local state:

import { useState } from "react";
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>+</button>
<button onClick={() => setCount(count - 1)}>-</button>
<button onClick={() => setCount(0)}>Reset</button>
</div>
);
}
function Form() {
const [name, setName] = useState("");
const [email, setEmail] = useState("");
const [age, setAge] = useState(0);
// Hoặc dùng object
const [user, setUser] = useState({
name: "",
email: "",
age: 0,
});
const updateUser = (field, value) => {
setUser((prev) => ({ ...prev, [field]: value }));
};
}

Side effects (API calls, subscriptions, timers):

import { useEffect, useState } from "react";
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
// Chạy sau mỗi render
console.log("Component rendered");
});
useEffect(() => {
// Chỉ chạy lần đầu (mount)
console.log("Component mounted");
}, []);
useEffect(() => {
// Chạy khi userId thay đổi
setLoading(true);
fetch(`/api/users/${userId}`)
.then((r) => r.json())
.then((data) => {
setUser(data);
setLoading(false);
});
}, [userId]);
useEffect(() => {
// Cleanup function
const timer = setInterval(() => {
console.log("Tick");
}, 1000);
return () => {
clearInterval(timer); // Cleanup khi unmount
};
}, []);
if (loading) return <div>Loading...</div>;
return <div>{user?.name}</div>;
}

Chia sẻ data qua component tree:

import { createContext, useContext, useState } from "react";
// Tạo context
const ThemeContext = createContext();
// Provider
function App() {
const [theme, setTheme] = useState("light");
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
<Toolbar />
</ThemeContext.Provider>
);
}
// Consumer
function Toolbar() {
const { theme, setTheme } = useContext(ThemeContext);
return (
<div className={theme}>
<button onClick={() => setTheme(theme === "light" ? "dark" : "light")}>Toggle Theme</button>
</div>
);
}

Giữ reference đến DOM hoặc mutable value:

import { useRef, useEffect } from "react";
function TextInput() {
const inputRef = useRef(null);
useEffect(() => {
// Focus input khi mount
inputRef.current.focus();
}, []);
return <input ref={inputRef} type="text" />;
}
// Lưu giá trị không trigger re-render
function Timer() {
const countRef = useRef(0);
useEffect(() => {
const timer = setInterval(() => {
countRef.current += 1;
console.log(countRef.current);
}, 1000);
return () => clearInterval(timer);
}, []);
}

Memoize expensive calculations:

import { useMemo, useState } from "react";
function ExpensiveComponent({ items }) {
const [filter, setFilter] = useState("");
// Chỉ tính lại khi items hoặc filter thay đổi
const filteredItems = useMemo(() => {
console.log("Filtering...");
return items.filter((item) => item.name.toLowerCase().includes(filter.toLowerCase()));
}, [items, filter]);
return (
<div>
<input value={filter} onChange={(e) => setFilter(e.target.value)} />
{filteredItems.map((item) => (
<div key={item.id}>{item.name}</div>
))}
</div>
);
}

Memoize functions:

import { useCallback, useState } from "react";
function Parent() {
const [count, setCount] = useState(0);
// Function reference không đổi
const handleClick = useCallback(() => {
setCount((c) => c + 1);
}, []);
return <Child onClick={handleClick} />;
}
// Child chỉ re-render khi onClick thay đổi
const Child = React.memo(({ onClick }) => {
console.log("Child rendered");
return <button onClick={onClick}>Click</button>;
});

State phức tạp với reducer pattern:

import { useReducer } from "react";
const initialState = { count: 0 };
function reducer(state, action) {
switch (action.type) {
case "increment":
return { count: state.count + 1 };
case "decrement":
return { count: state.count - 1 };
case "reset":
return initialState;
default:
throw new Error();
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
Count: {state.count}
<button onClick={() => dispatch({ type: "increment" })}>+</button>
<button onClick={() => dispatch({ type: "decrement" })}>-</button>
<button onClick={() => dispatch({ type: "reset" })}>Reset</button>
</>
);
}

Tái sử dụng logic:

// useFetch custom hook
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
setLoading(true);
fetch(url)
.then((r) => r.json())
.then((data) => {
setData(data);
setLoading(false);
})
.catch((err) => {
setError(err);
setLoading(false);
});
}, [url]);
return { data, loading, error };
}
// Sử dụng
function UserList() {
const { data, loading, error } = useFetch("/api/users");
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return (
<ul>
{data.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
// useLocalStorage
function useLocalStorage(key, initialValue) {
const [value, setValue] = useState(() => {
const stored = localStorage.getItem(key);
return stored ? JSON.parse(stored) : initialValue;
});
useEffect(() => {
localStorage.setItem(key, JSON.stringify(value));
}, [key, value]);
return [value, setValue];
}
// useDebounce
function useDebounce(value, delay) {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const timer = setTimeout(() => {
setDebouncedValue(value);
}, delay);
return () => clearTimeout(timer);
}, [value, delay]);
return debouncedValue;
}
// useWindowSize
function useWindowSize() {
const [size, setSize] = useState({
width: window.innerWidth,
height: window.innerHeight,
});
useEffect(() => {
const handleResize = () => {
setSize({
width: window.innerWidth,
height: window.innerHeight,
});
};
window.addEventListener("resize", handleResize);
return () => window.removeEventListener("resize", handleResize);
}, []);
return size;
}
  1. Chỉ gọi ở top level - Không trong loops, conditions, nested functions
  2. Chỉ gọi trong React functions - Components hoặc custom hooks
  3. Custom hooks bắt đầu với “use” - Convention naming
  • Ưu tiên useState cho simple state
  • Dùng useReducer cho complex state logic
  • useMemo/useCallback chỉ khi cần optimize
  • Tạo custom hooks để reuse logic
  • Cleanup trong useEffect để tránh memory leaks