Luyện Phỏng Vấn IT — 2000+ Câu Hỏi Phỏng Vấn IT Có Đáp Án 2026
React
JSX (JavaScript XML) là cú pháp mở rộng của JavaScript cho phép viết HTML bên trong JavaScript.
- React sử dụng JSX vì nó giúp mô tả UI trực quan hơn, kết hợp logic và markup trong cùng một file component.
- Babel transpile JSX thành các lời gọi React.createElement() trước khi chạy trên trình duyệt.
JSX được Babel transpile thành React.createElement(type, props, ...children).
Ví dụ: <h1 className='title'>Hello</h1> trở thành React.createElement('h1', { className: 'title' }, 'Hello'). Kể từ React 17, có thể sử dụng JSX mà không cần import React nhờ transform tự động.
Có nhiều cách: toán tử ba ngôi isLoggedIn ? <Dashboard /> : <Login />, toán tử && isLoading && <Spinner />, hoặc if/else bên ngoài return.
- Trong thực tế, ternary dùng cho toggle giữa 2 component, && dùng để hiển thị/ẩn một component.
- Cần tránh
count && <List />vì nếu count=0, React render số 0 thay vì không render gì.
React Fragment (<React.Fragment> hoặc <>) cho phép nhóm nhiều phần tử mà không thêm node DOM thừa.
- Sử dụng khi component cần trả về nhiều phần tử cùng cấp, ví dụ trong table rows hay danh sách.
- Cú pháp rút gọn
<>...</>không hỗ trợ thuộc tính key, cần dùng<React.Fragment key={id}>khi render danh sách.
Key là prop đặc biệt giúp React xác định phần tử nào trong danh sách đã thay đổi, được thêm hoặc bị xóa.
- React sử dụng key để tối ưu hóa quá trình reconciliation, tránh re-render không cần thiết.
- Key phải là chuỗi hoặc số duy nhất trong cùng cấp độ, không nên dùng index khi danh sách có thể thay đổi thứ tự.
JSX chỉ chấp nhận biểu thức (expressions) trong {}, không nhận câu lệnh (statements) như if, for, while vì JSX được transpile thành lời gọi hàm.
Ví dụ: {items.map(i => <li key={i.id}>{i.name}</li>)} hợp lệ, nhưng {for(...)} thì không. Dùng ternary cho điều kiện, map/filter để lặp. Nếu logic phức tạp, tính toán bên ngoài return rồi dùng biến trong JSX.
Trong JSX, tên event dùng camelCase (onClick, onChange, onSubmit) thay vì lowercase.
- Handler là function reference, không phải string.
- Để ngăn hành vi mặc định, gọi e.preventDefault() trong handler thay vì return false.
- React sử dụng synthetic events để đảm bảo tính nhất quán giữa các trình duyệt.
Dùng {...props} để truyền tất cả thuộc tính của một object xuống component con.
Ví dụ: <Button {...buttonProps} /> tương đương với liệt kê từng prop riêng lẻ. Hữu ích khi cần forward props nhưng cần cẩn thận vì có thể truyền props không mong muốn.
React bỏ qua và không render null, undefined, false — đây là cơ chế cho conditional rendering.
- Chuỗi rỗng
''cũng không hiển thị gì. - Tuy nhiên số
0sẽ được render thành chữ số 0 trên màn hình — đây là lỗi hay gặp khi viết{count && <List />}và count bằng 0. - Cách fix: dùng ternary
{count > 0 ? <List /> : null}để luôn trả về boolean hoặc null.
props.children là nội dung được truyền giữa thẻ mở và đóng của component.
Ví dụ: <Card><p>Nội dung</p></Card> thì Card nhận <p>Nội dung</p> qua props.children và render bằng {props.children}. Dùng để tạo wrapper component linh hoạt như Card, Modal, Layout mà không cần biết trước nội dung bên trong. Nếu không truyền gì, children là undefined — nên khai báo defaultProps hoặc kiểm tra trước khi render.
Controlled Component là component mà giá trị của form element (input, textarea, select) được kiểm soát bởi React state.
- Mỗi thay đổi của input gọi onChange handler để cập nhật state, và value của input được bind với state.
- Đây là cách được khuyến nghị vì cho phép validate và xử lý dữ liệu trước khi submit.
Stateless (presentational) component chỉ nhận props và render UI thuần túy, không giữ state nội bộ — dễ test vì output chỉ phụ thuộc vào input.
Ví dụ: <Button label='Save' onClick={fn} />. Stateful (container) component quản lý state và business logic, thường fetch data và pass xuống. Phân biệt này mờ dần từ khi Hooks ra đời — function component giờ có thể có state qua useState và vẫn được coi là một loại component duy nhất.
Component có thể return: JSX element, null (không render gì), array of elements, string, number, Portal, Fragment.
- Return null hữu ích để ẩn component mà không thay đổi cấu trúc.
- Array cần có key cho mỗi element.
- String và number được render trực tiếp như text node.
Props là dữ liệu được truyền từ component cha xuống con, read-only và không thể thay đổi bởi component nhận.
- State là dữ liệu nội bộ của component, có thể thay đổi theo thời gian và kích hoạt re-render.
- Props giống như tham số hàm, state giống như biến cục bộ của component.
componentDidMount được gọi một lần duy nhất ngay sau khi component được gắn vào DOM lần đầu. Đây là nơi thích hợp để: fetch dữ liệu từ API, đăng ký event listeners, khởi tạo thư viện cần DOM như D3 hay Google Maps. Tương đương useEffect(() => { fetchData(); }, []) trong function component.
Lưu ý: gọi setState bên trong sẽ trigger thêm một lần render nhưng user không thấy trạng thái trung gian.
componentWillUnmount được gọi ngay trước khi component bị xóa khỏi DOM — đây là nơi bắt buộc phải cleanup để tránh memory leak.
Ví dụ: clearInterval(this.timerId), hủy WebSocket subscription, gọi controller.abort() để cancel fetch đang chạy. Không được gọi setState ở đây vì component không còn tồn tại. Tương đương hàm cleanup return () => clearInterval(id) trả về trong useEffect.
useState nhận giá trị khởi tạo và trả về mảng gồm state hiện tại và setter function.
- Khi gọi setter, React schedule re-render với giá trị mới.
- Setter có thể nhận giá trị mới trực tiếp hoặc updater function để tránh stale closure.
const Counter = () => {
const [count, setCount] = useState(0)
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>+1 trực tiếp</button>
<button onClick={() => setCount(prev => prev + 1)}>+1 updater</button>
</div>
)
}Dependency array kiểm soát khi nào effect chạy lại.
- Không có array: chạy sau mỗi render.
- Array rỗng
[]: chỉ chạy sau mount, tương đương componentDidMount. - Array có giá trị
[a, b]: chạy khi a hoặc b thay đổi. - React ESLint plugin exhaustive-deps giúp phát hiện missing dependencies.
Vì JSX được transpile thành một lời gọi hàm React.createElement() đơn, và hàm chỉ có thể return một giá trị.
- Để return nhiều element, dùng Fragment (
<>...</>) hoặc bọc trong một wrapper element như<div>. - Fragment là lựa chọn tốt hơn vì không thêm node DOM không cần thiết.
Pure Component trong Class Component là React.PureComponent, tự động implement shouldComponentUpdate với shallow comparison của props và state.
- Trong Function Component, tương đương là React.memo() wrap component.
- Hữu ích để tránh re-render không cần thiết khi props không thay đổi.
- Shallow comparison có thể bỏ qua thay đổi của nested objects.
Composition là kỹ thuật xây dựng component phức tạp từ các component đơn giản hơn thay vì kế thừa.
- React khuyến khích composition vì nó linh hoạt hơn, dễ test hơn và tránh các vấn đề của prototype chain.
- Patterns như children prop, render props, và slots đều là biểu hiện của composition.
Compound Components là pattern cho phép các component liên quan chia sẻ state ngầm qua Context.
Ví dụ: <Select>, <Select.Option> chia sẻ state selected value mà không cần props drilling. Pattern này tạo API linh hoạt, cho phép user sắp xếp sub-components theo ý muốn trong khi vẫn duy trì shared state.
Mount là khi component được thêm vào DOM lần đầu tiên.
- Update xảy ra khi props hoặc state thay đổi, khiến component re-render.
- Unmount là khi component bị xóa khỏi DOM.
- Các lifecycle methods (componentDidMount, componentDidUpdate, componentWillUnmount) hoặc useEffect hook xử lý các giai đoạn này.
Lifting state up là kỹ thuật di chuyển state lên component cha chung gần nhất khi nhiều component cần chia sẻ cùng dữ liệu.
Ví dụ: TemperatureInput Celsius và Fahrenheit đều cần cùng nhiệt độ — state được đặt ở component cha, truyền xuống qua props và cập nhật qua callback onTemperatureChange. Cần làm khi hai sibling component mất đồng bộ. Nếu phải lift quá nhiều tầng, hãy xem xét Context hoặc state manager.
Immutability nghĩa là không trực tiếp mutate state mà tạo object/array mới.
- React dùng shallow comparison để detect thay đổi, nếu mutate trực tiếp React sẽ không nhận ra sự thay đổi và không re-render.
- Dùng spread operator
{...state, field: value}hoặc Array.map/filter để tạo bản sao mới.
Dùng spread operator để tạo bản sao ở mỗi cấp: setState(prev => ({ ...prev, user: { ...prev.user, name: 'new' } })).
- Với nested sâu, xem xét dùng Immer library cho phép viết code mutate nhưng tạo immutable update dưới hood.
- Cấu trúc state phẳng (flat) cũng giúp tránh vấn đề này.
Derived state là state được tính toán từ props.
- Vấn đề: khi dùng getDerivedStateFromProps sai cách, component có thể mất state khi props thay đổi.
- React khuyến nghị: tính toán giá trị derived trong render thay vì lưu vào state, hoặc dùng fully controlled/uncontrolled với key prop để reset.
- Tránh anti-pattern copy props vào state.
Dữ liệu trong React chỉ chảy một chiều từ cha xuống con qua props, không bao giờ ngược lại.
- Con muốn cập nhật state cha phải gọi callback được truyền xuống — ví dụ
<Input onChange={handleChange} />và cha xử lý tronghandleChange. - Pattern này giúp dễ trace bug vì chỉ có một nguồn dữ liệu duy nhất (single source of truth), tránh circular data flow như two-way binding của Angular 1.
- Khi app lớn, kết hợp với Context hay Redux để quản lý state toàn cục.
- Controlled component: giá trị form được kiểm soát bởi React state, phải có onChange handler, phù hợp khi cần validation hay transform dữ liệu.
- Uncontrolled component: giá trị lưu trong DOM, dùng ref để đọc, đơn giản hơn cho form không cần real-time validation.
React khuyến nghị Controlled trong hầu hết trường hợp.
componentDidUpdate(prevProps, prevState) được gọi sau mỗi lần re-render (trừ lần đầu mount).
- So sánh prevProps/prevState với giá trị hiện tại để quyết định có nên thực hiện side effects không.
- Luôn wrap code trong điều kiện
if (prevProps.id !== this.props.id)để tránh infinite loop.
componentDidCatch(error, info) là lifecycle method cho phép Class Component bắt lỗi từ component con trong render phase.
- Kết hợp với getDerivedStateFromError để tạo Error Boundary - component bắt lỗi và hiển thị fallback UI.
- Function components không thể là Error Boundary, phải dùng class hoặc thư viện như react-error-boundary.
Thứ tự lifecycle methods qua 3 giai đoạn:
- Mount: constructor → getDerivedStateFromProps → render → DOM update → componentDidMount.
- Update: getDerivedStateFromProps → shouldComponentUpdate → render → getSnapshotBeforeUpdate → DOM update → componentDidUpdate.
- Unmount: componentWillUnmount → component bị xóa.
- Với concurrent mode, render phase có thể bị interrupt.
Cleanup function là function được return từ useEffect, chạy trước khi effect chạy lại hoặc component unmount.
Dùng để hủy subscriptions, clearTimeout, cancel fetch requests tránh memory leaks và stale updates.
useEffect(() => {
const timer = setInterval(() => {
setCount(c => c + 1)
}, 1000)
const subscription = eventSource.subscribe(handler)
// cleanup: chạy khi unmount hoặc trước khi effect chạy lại
return () => {
clearInterval(timer)
subscription.unsubscribe()
}
}, [])Không thể trực tiếp dùng async function làm effect callback vì nó trả về Promise, nhưng cleanup phải return void.
- Giải pháp: khai báo async function bên trong effect rồi gọi nó.
- Luôn xử lý cleanup để cancel request nếu component unmount.
useEffect(() => {
const controller = new AbortController()
const fetchData = async () => {
try {
const res = await fetch('/api/data', { signal: controller.signal })
const json = await res.json()
setData(json)
} catch (err) {
if (err.name !== 'AbortError') setError(err)
}
}
fetchData()
return () => controller.abort()
}, [url])Infinite loop xảy ra khi effect cập nhật state mà state đó lại là dependency của effect.
Giải pháp: kiểm tra dependency array có chính xác không, dùng functional updater setCount(c => c + 1) thay vì reference state trong effect, tách effect thành nhiều effect với dependencies khác nhau, hoặc dùng useRef để lưu giá trị mà không trigger re-render.
Stale closure xảy ra khi effect capture giá trị cũ của state hoặc props vì closure bị tạo lúc render trước.
Giải pháp: thêm dependencies bị thiếu vào array, dùng useRef để lưu giá trị mới nhất không cần re-run effect, hoặc dùng functional updater setState(prev => ...) để không cần reference state trong closure.
Trong React 18 Strict Mode development, mỗi component mount-unmount-mount lại để test cleanup.
- Effect chạy: mount (lần 1) → cleanup → mount (lần 2).
- Giúp phát hiện bugs do missing cleanup.
- Production không bị ảnh hưởng.
- Code đúng cần idempotent: connect/disconnect subscription, fetch với cancel, v.v.
useEffect chạy asynchronously sau khi trình duyệt paint, không block UI. useLayoutEffect chạy synchronously sau DOM mutations nhưng trước khi trình duyệt paint, có thể block paint.
- Dùng useLayoutEffect khi cần đọc layout DOM và synchronously re-render để tránh visual flicker (ví dụ tooltips).
- Mặc định dùng useEffect.
Trích xuất logic vào custom hook - function bắt đầu bằng 'use'. Custom hook có thể gọi useState, useEffect và các hooks khác. Component sử dụng custom hook sẽ có state riêng biệt, không chia sẻ instance.
Ví dụ: useWindowSize(), useDebounce(), useFetch() đều là custom hooks phổ biến.
Dùng AbortController để cancel fetch request cũ khi effect re-run: const controller = new AbortController(); fetch(url, { signal: controller.signal }); return () => controller.abort().
- Hoặc dùng boolean flag
let cancelled = falsetrong cleanup. - React Query và SWR tự động handle race conditions.
useContext nhận Context object và trả về value hiện tại từ Provider gần nhất bên trên.
- Khi value của Provider thay đổi, mọi component gọi useContext đó đều re-render.
Pitfall: nếu không có Provider bên trên, trả về giá trị default từ createContext.
// 1. Tạo context
const ThemeContext = createContext<'light' | 'dark'>('light')
// 2. Provider bọc cây component
const App = () => (
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
)
// 3. Consume ở bất kỳ component con nào
const Toolbar = () => {
const theme = useContext(ThemeContext)
return <div className={theme}>Current theme: {theme}</div>
}useReducer phù hợp khi state logic phức tạp với nhiều sub-values, hoặc nhiều actions khác nhau cập nhật state theo cách khác nhau.
Giống Redux pattern với dispatch/action/reducer.
type State = { count: number; step: number }
type Action = { type: 'increment' } | { type: 'setStep'; payload: number }
const reducer = (state: State, action: Action): State => {
switch (action.type) {
case 'increment': return { ...state, count: state.count + state.step }
case 'setStep': return { ...state, step: action.payload }
default: return state
}
}
const Counter = () => {
const [state, dispatch] = useReducer(reducer, { count: 0, step: 1 })
return (
<>
<p>Count: {state.count}, Step: {state.step}</p>
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
</>
)
}useMemo memoize kết quả tính toán tốn kém, chỉ tính lại khi dependencies thay đổi.
- Dùng khi có tính toán expensive mà không muốn chạy lại mỗi render, hoặc cần stable object reference.
- Không nên dùng quá mức vì có overhead riêng.
const ProductList = ({ products, filter }: Props) => {
// tính toán chỉ chạy lại khi products hoặc filter thay đổi
const filtered = useMemo(
() => products.filter(p => p.category === filter),
[products, filter]
)
return <ul>{filtered.map(p => <li key={p.id}>{p.name}</li>)}</ul>
}useCallback memoize function để giữ stable reference giữa renders.
Dùng khi truyền callback xuống child component được wrap bởi React.memo, hoặc khi function là dependency của useEffect.
const Parent = () => {
const [count, setCount] = useState(0)
// không dùng useCallback: handleClick mới mỗi render → Child luôn re-render
// dùng useCallback: reference ổn định → Child skip re-render nếu props khác không đổi
const handleClick = useCallback(() => {
console.log('clicked')
}, []) // deps rỗng vì không dùng state/props trong fn
return (
<>
<button onClick={() => setCount(c => c + 1)}>Parent count: {count}</button>
<MemoChild onClick={handleClick} />
</>
)
}
const MemoChild = React.memo(({ onClick }: { onClick: () => void }) => {
console.log('Child render')
return <button onClick={onClick}>Child</button>
})useRef trả về mutable ref object với .current property không trigger re-render khi thay đổi.
Hai công dụng chính:
- truy cập DOM element trực tiếp để focus/measure;
- lưu giá trị mutable giữa renders mà không cần re-render
const FormExample = () => {
// (1) DOM ref: focus input khi mount
const inputRef = useRef<HTMLInputElement>(null)
useEffect(() => { inputRef.current?.focus() }, [])
// (2) mutable value: lưu timerId mà không trigger re-render
const timerRef = useRef<ReturnType<typeof setInterval> | null>(null)
const start = () => { timerRef.current = setInterval(tick, 1000) }
const stop = () => { if (timerRef.current) clearInterval(timerRef.current) }
return <input ref={inputRef} placeholder="auto-focused" />
}Hai quy tắc bắt buộc:
- (1) Chỉ gọi hooks ở top level — không trong vòng lặp, điều kiện hay hàm lồng, vì React theo dõi hooks theo thứ tự gọi mỗi render.
- (2) Chỉ gọi hooks trong function component hoặc custom hook, không trong class hay hàm JS thông thường.
Vi phạm gây lỗi runtime khó debug. Cài eslint-plugin-react-hooks để bắt lỗi ngay lúc viết code thay vì lúc chạy.
Khi context value thay đổi, mọi consumer re-render dù chỉ dùng một phần.
- Tối ưu: tách context thành nhiều context nhỏ hơn theo concern, memoize context value với useMemo, dùng context selector pattern hay thư viện như use-context-selector.
- Ngoài ra, split static và dynamic context riêng biệt.
useMemo là hook memoize giá trị/kết quả tính toán bên trong component.
- React.memo là HOC memoize toàn bộ component, skip re-render khi props không thay đổi (shallow comparison).
- Kết hợp cả hai: dùng React.memo cho component, useCallback cho callback props để React.memo hoạt động hiệu quả.
Custom hook là JavaScript function bắt đầu bằng 'use' có thể gọi các hooks khác. Dùng để tái sử dụng stateful logic giữa components. Mỗi component sử dụng custom hook có state instance riêng biệt, không chia sẻ.
Ví dụ: function useLocalStorage(key, initial) { const [value, setValue] = useState(...); return [value, setValue]; }.
Dùng custom hook khi logic cần: React hooks (useState, useEffect, v.v.), React lifecycle, hay stateful behavior.
- Dùng utility function (pure function) khi: chỉ xử lý data transformation, validation, formatting - không cần React context.
- Custom hook gọi được từ components, utility function gọi từ bất cứ đâu.
useDebounce delay việc cập nhật value cho đến khi user ngừng thay đổi trong khoảng thời gian nhất định:
function useDebounce<T>(value: T, delay: number): T {
const [debounced, setDebounced] = useState(value)
useEffect(() => {
const timer = setTimeout(() => setDebounced(value), delay)
return () => clearTimeout(timer)
}, [value, delay])
return debounced
}usePrevious lưu giá trị trước đó của một value sử dụng useRef.
Sau mỗi render, ref được cập nhật với value hiện tại nhưng function trả về ref.current (value từ render trước).
function usePrevious<T>(value: T): T | undefined {
const ref = useRef<T | undefined>(undefined)
// useEffect không có deps chạy sau MỖI render
// → ghi ref SAU khi render hiện tại đã xong
// → lần render tiếp theo, ref.current là giá trị CŨ
useEffect(() => {
ref.current = value
})
return ref.current
}
// Ví dụ sử dụng
const Counter = () => {
const [count, setCount] = useState(0)
const prevCount = usePrevious(count)
return (
<p>
Current: {count}, Previous: {prevCount ?? 'none'}
</p>
)
}Context API cho phép chia sẻ dữ liệu (theme, language, auth, user) qua component tree mà không cần pass props qua từng cấp trung gian (props drilling).
Gồm ba phần: createContext, Provider, useContext.
// auth-context.tsx
interface AuthCtx { user: User | null; logout: () => void }
const AuthContext = createContext<AuthCtx | null>(null)
export const AuthProvider = ({ children }: { children: React.ReactNode }) => {
const [user, setUser] = useState<User | null>(null)
const logout = () => setUser(null)
return (
<AuthContext.Provider value={{ user, logout }}>
{children}
</AuthContext.Provider>
)
}
// Hook để dùng an toàn
export const useAuth = () => {
const ctx = useContext(AuthContext)
if (!ctx) throw new Error('useAuth must be inside AuthProvider')
return ctx
}
// Dùng trong component
const Header = () => {
const { user, logout } = useAuth()
return <button onClick={logout}>Logout {user?.name}</button>
}Dùng Context khi: dữ liệu cần ở nhiều levels sâu (theme, locale, auth), data được nhiều components dùng, props chỉ passed qua để forward (không dùng ở cấp trung gian).
- Không nên dùng cho state thay đổi thường xuyên vì gây nhiều re-renders.
- Props drilling 2-3 cấp thì không cần Context.
useParams trả về object chứa các dynamic segment của URL route đang active.
Ví dụ route /users/:id khi truy cập /users/42 thì const { id } = useParams() trả về id = '42'.
- Lưu ý giá trị luôn là string, cần tự convert nếu cần number:
Number(id). - Với TypeScript dùng
useParams<{ id: string }>()để type-safe. - Nếu dùng ngoài Router context sẽ trả về object rỗng.
useNavigate trả về navigate function để programmatically điều hướng: navigate('/path'), navigate(-1) (back), navigate('/path', { replace: true }).
- Thay thế cho useHistory trong v5.
- Dùng khi cần navigate sau form submit, sau auth login, hay trong event handlers không phải JSX.
Nested routes dùng <Outlet /> component để render child routes.
- Parent route render layout, Outlet là nơi child route render:
<Route path='/dashboard' element={<DashboardLayout />}><Route index element={<Overview />} /><Route path='settings' element={<Settings />} /></Route>. - Child route paths relative, không cần lặp lại parent path.
Tạo wrapper component kiểm tra auth rồi redirect nếu chưa đăng nhập: function PrivateRoute({ children }) { const { user } = useAuth(); return user ? children : <Navigate to='/login' state={{ from: location }} replace />; }.
- Truyền
state={{ from: location }}để sau khi login có thể redirect về trang user định vào. - Dùng trong Route:
<Route path='/dashboard' element={<PrivateRoute><Dashboard /></PrivateRoute>} />. - Navigate replace thay Redirect của React Router v5.
Tổ chức nhiều context bằng cách: tạo nhiều Provider riêng biệt, nest chúng lại (thứ tự từ ngoài vào trong), hoặc tạo AppProviders component bọc tất cả providers.
- Mỗi context chịu trách nhiệm một concern (AuthContext, ThemeContext, CartContext).
- Tránh god context chứa tất cả global state.
Dùng useSearchParams hook: const [searchParams, setSearchParams] = useSearchParams().
- Đọc:
searchParams.get('tab'). - Cập nhật:
setSearchParams({ tab: 'new' })haysetSearchParams(prev => { prev.set('tab', 'new'); return prev; }). - Phù hợp cho filter, pagination state cần share qua URL.
Uncontrolled component lưu giá trị trong DOM, không sync với React state.
- Dùng ref để đọc khi cần (submit):
const inputRef = useRef(null); <input ref={inputRef} />. - Kể từ React 19, useRef yêu cầu truyền giá trị khởi tạo (thường là null cho DOM refs).
- Đơn giản hơn cho form không cần real-time validation, nhưng khó tích hợp với validation libraries.
Formik là thư viện quản lý form state, validation, và submission trong React.
- Xử lý: touched/dirty states, error messages, submission handling, field-level và form-level validation.
- Giảm boilerplate code đáng kể so với quản lý state thủ công.
- Tích hợp tốt với Yup cho schema validation.
React Hook Form dùng uncontrolled components và refs thay vì state, nên ít re-renders hơn (performance tốt hơn).
- API đơn giản hơn với register, handleSubmit, formState.
- Bundle size nhỏ hơn Formik.
- Formik dễ học hơn với explicit controlled pattern.
- RHF được cộng đồng ưa chuộng hơn cho performance-critical forms.
Các cách:
- (1) Manual: check trong onChange/onSubmit, lưu errors vào state.
- (2) HTML5 built-in: required, minLength, pattern attributes.
- (3) Formik + Yup: declarative schema validation.
- (4) React Hook Form với resolver (Yup, Zod).
Dùng Zod cho type-safe validation kết hợp TypeScript là best practice hiện đại.
Error Boundary là React component bắt JavaScript errors trong component tree con và hiển thị fallback UI thay vì crash toàn app.
- Chỉ có thể tạo với Class Component implement
getDerivedStateFromError(set error state) vàcomponentDidCatch(log error). - Thư viện react-error-boundary cung cấp
ErrorBoundarycomponent tiện dụng hơn.
Error Boundary không bắt: errors trong event handlers (phải try/catch thủ công), async code (setTimeout, Promises), server-side rendering, errors trong chính Error Boundary component.
Chỉ bắt errors trong render phase, lifecycle methods, constructors của component tree bên dưới.
Lưu array of field objects trong state.
- Render với map, mỗi field có unique id làm key.
- Thêm:
setFields(prev => [...prev, { id: uuid(), value: '' }]). - Xóa:
setFields(prev => prev.filter(f => f.id !== id)). - React Hook Form cung cấp useFieldArray hook xử lý pattern này với performance tốt hơn.
Dùng @hookform/resolvers/zod với zodResolver: const { register, handleSubmit } = useForm({ resolver: zodResolver(schema) }).
- Define schema:
const schema = z.object({ email: z.string().email(), age: z.number().min(18) }). - Errors type-safe từ Zod được truyền vào formState.errors.
- Cách tiếp cận hiện đại nhất cho type-safe forms.
React.memo là HOC memoize function component, skip re-render nếu props không thay đổi (shallow comparison).
- Nên dùng khi component render expensive và props ít thay đổi.
- Cần kết hợp với useCallback cho function props để có hiệu quả.
// Row chỉ re-render khi data hoặc onDelete thực sự thay đổi
const Row = React.memo(({ data, onDelete }: RowProps) => {
console.log('Row render:', data.id)
return (
<tr>
<td>{data.name}</td>
<td><button onClick={() => onDelete(data.id)}>Delete</button></td>
</tr>
)
})
const Table = ({ items }: { items: Item[] }) => {
// useCallback để giữ stable reference, tránh Row re-render mỗi khi Table render
const handleDelete = useCallback((id: number) => {
setItems(prev => prev.filter(i => i.id !== id))
}, [])
return (
<table>
{items.map(item => <Row key={item.id} data={item} onDelete={handleDelete} />)}
</table>
)
}React.lazy cho phép lazy load component, chỉ download JavaScript khi component cần render.
- Suspense hiển thị fallback UI trong khi component đang load.
- Giúp code splitting, giảm initial bundle size.
import { lazy, Suspense } from 'react'
// Bundle của HeavyChart chỉ download khi cần
const HeavyChart = lazy(() => import('./HeavyChart'))
const Dashboard = () => {
const [showChart, setShowChart] = useState(false)
return (
<div>
<button onClick={() => setShowChart(true)}>Load Chart</button>
{showChart && (
<Suspense fallback={<div>Loading chart...</div>}>
<HeavyChart />
</Suspense>
)}
</div>
)
}Virtual DOM là representation trong memory của DOM thực.
- Khi state thay đổi, React tạo Virtual DOM mới, so sánh (diff) với Virtual DOM cũ, tính ra minimal set of changes, rồi áp dụng vào DOM thực (reconciliation).
- Giúp tối ưu DOM operations tốn kém, nhưng không phải lúc nào cũng nhanh hơn direct DOM manipulation.
Render props là technique chia sẻ code giữa components dùng prop là function trả về React element.
- Component nhận function prop gọi nó với internal state/logic:
<Mouse render={({ x, y }) => <Cat x={x} y={y} />} />. - Linh hoạt hơn HOC vì tránh naming collisions.
- Custom hooks giờ thường là giải pháp cleaner hơn.
Virtualization chỉ render items hiện trong viewport, không render toàn bộ danh sách hàng nghìn items.
- Thư viện: react-window (lightweight), react-virtualized (feature-rich).
- Giảm DOM nodes từ hàng nghìn xuống chỉ ~20-50.
- Cần thiết khi render danh sách lớn (>100 items) gây performance issues.
Định nghĩa component bên trong component khác tạo new component type mỗi render, React unmount/remount thay vì update, gây mất state và performance issues.
- Luôn khai báo components ở top-level module, hoặc ngoài parent component.
- Nếu cần data từ parent, truyền qua props hoặc dùng children pattern.
Thay đổi key của component khiến React unmount component cũ và mount component mới hoàn toàn, reset tất cả state.
- Kỹ thuật hữu ích để: reset form sau submit
<Form key={formKey} />, reset uncontrolled component khi data source thay đổi. - Đây là cách React có ý muốn để 'reset' component state từ bên ngoài.
useState phù hợp cho state đơn giản (boolean, string, number). useReducer cho state phức tạp có nhiều sub-values hoặc khi next state phụ thuộc previous state.
useReducer hay dùng cho form phức tạp, wizard multi-step, hoặc khi state logic cần tách riêng để test.
useMemo cache kết quả tính toán: useMemo(() => expensiveCalc(data), [data]). useCallback cache function reference: useCallback(() => handleClick(id), [id]). useCallback dùng khi truyền callback xuống child component dùng React.memo.
Lạm dụng 2 hooks này có thể giảm performance do overhead.
Cleanup chạy:
- trước mỗi lần effect re-run khi dependencies thay đổi
- khi component unmount. Quan trọng để tránh memory leaks: hủy subscriptions, abort fetch requests, clear timers
Ví dụ: return () => controller.abort() trong fetch effect.
Custom hooks là functions bắt đầu bằng use, tái sử dụng logic stateful giữa components.
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
fetch(url)
.then(r => r.json())
.then(setData)
.catch(setError)
.finally(() => setLoading(false));
}, [url]);
return { data, loading, error };
}Dùng khi component nhận props ổn định, render tốn kém, và parent re-render thường xuyên mà props không đổi.
KHÔNG dùng khi: component luôn nhận props mới (objects/arrays tạo mới), component render nhẹ, hoặc dùng inline callbacks (phá vỡ memo).
<Child style={{color: 'red'}}/> tạo object mới mỗi render → Child luôn re-render dù dùng React.memo.
- Fix: khai báo object ngoài component hoặc useMemo.
- Tương tự với
onClick={() => handle(id)}→ dùng useCallback hoặc data attribute.
Components chia sẻ state ngầm qua Context.
Ví dụ: <Tabs><Tabs.List><Tabs.Tab>A</Tabs.Tab></Tabs.List><Tabs.Panel>Content A</Tabs.Panel></Tabs>. Flexible API, user kiểm soát layout. Dùng cho: Tabs, Accordion, Dropdown, Select.
Cách tổ chức phổ biến và hiệu quả nhất là theo feature-based, trong đó mỗi tính năng có folder riêng như src/features/auth/ hay src/features/dashboard/, bên trong chứa đầy đủ components, hooks, services, và types của tính năng đó.
Phần dùng chung được đặt ở src/components/ cho UI components, src/hooks/ cho custom hooks, src/utils/ cho helper functions, và src/services/ cho API calls. Mỗi folder nên có file index.ts làm barrel export để import gọn gàng hơn.
Cách tổ chức này tốt hơn nhiều so với cấu trúc theo type (gom tất cả components vào một folder, tất cả hooks vào một folder) vì khi dự án lớn lên, feature-based giúp tìm và sửa code liên quan đến một tính năng dễ dàng hơn rất nhiều.
Continuous re-render thường do reference instability hoặc missing dependency — dùng DevTools Profiler để identify.
- React DevTools Profiler: tìm component render nhiều.
- Nguyên nhân phổ biến: tạo object/array mới trong render, missing dependency array useEffect, context value thay đổi.
- Fix: React.memo, useMemo/useCallback, split context, move state xuống component con.
React 18 Strict Mode gọi useEffect 2 lần trong development để detect side effects.
Fix:
- Đây là behavior đúng, production chỉ gọi 1 lần.
- Dùng cleanup function abort previous request.
- Hoặc dùng React Query/SWR thay vì fetch trực tiếp trong useEffect
- Mutate object/array trực tiếp thay vì tạo copy mới (React dùng reference comparison).
- State nằm trong stale closure.
- Dùng ref thay vì state
Fix: setState([...arr]) hoặc setState({...obj, key: value}) tạo reference mới.
Immer giúp tránh lỗi này.
Phát hiện: Chrome DevTools > Memory tab, check heap snapshots.
Nguyên nhân:
- Quên cleanup subscriptions/timers trong useEffect.
- Event listeners không remove.
- Abort controller không cancel fetch
Fix: return cleanup function trong useEffect.
Pattern: const controller = new AbortController(); return () => controller.abort();
Khi trang web load chậm, cần debug có hệ thống từ tổng quan đến chi tiết. Bước đầu tiên mở Chrome DevTools tab Network để xem waterfall — tìm các file quá lớn, requests bị blocking, hoặc API response chậm.
Tiếp theo chạy Lighthouse audit để nhận danh sách cụ thể các vấn đề cần khắc phục kèm mức độ ưu tiên. Sau đó dùng tab Performance để record và tìm long tasks (>50ms) gây block main thread, layout thrashing, hoặc excessive reflows. Cuối cùng chạy bundle analyzer để phát hiện dependencies nặng có thể thay thế hoặc lazy load.
Thứ tự fix nên theo impact: tối ưu critical rendering path trước, rồi code splitting, image optimization bằng format WebP/AVIF, và cuối cùng là caching strategy.
Với form nhiều fields, giải pháp tối ưu nhất là dùng React Hook Form vì nó hoạt động theo cơ chế uncontrolled components, chỉ re-render field nào thay đổi thay vì toàn bộ form — performance tốt hơn đáng kể so với controlled approach.
Kết hợp Zod schema để validate type-safe, cấu hình bằng pattern useForm({ resolver: zodResolver(schema) }) và bọc trong FormProvider để các nested components truy cập form state dễ dàng. Nếu form quá dài nên chia thành sections hoặc multi-step wizard, mỗi step validate riêng trước khi cho chuyển sang step tiếp theo.
Trong trường hợp cần logic state phức tạp hơn như conditional fields phụ thuộc lẫn nhau, có thể kết hợp thêm useReducer để quản lý các business rules.
CORS (Cross-Origin Resource Sharing) là cơ chế bảo mật của browser ngăn chặn frontend gọi API từ domain khác nếu server không cho phép — ví dụ app chạy ở localhost:3000 gọi API ở localhost:8080 sẽ bị chặn. Cách fix đúng là backend thêm header Access-Control-Allow-Origin chỉ định domain được phép, hoặc dùng thư viện cors trong Express với whitelist origins cụ thể.
Trong môi trường development, cấu hình proxy trong Vite (server.proxy) hoặc Next.js (rewrites) để requests đi qua cùng origin, tránh CORS hoàn toàn. Ở production, dùng API gateway hoặc reverse proxy như nginx để route requests.
Tuyệt đối không dùng CORS disable browser extension vì nó chỉ tắt bảo mật phía client mà không giải quyết gốc vấn đề.
Hydration mismatch xảy ra khi HTML render từ server khác với kết quả render lần đầu trên client, khiến React báo warning và có thể gây lỗi UI. Nguyên nhân phổ biến nhất là dùng browser-only APIs như window hay localStorage trong quá trình server render (server không có window), hoặc dùng Date/time mà timezone server khác client, hay random values cho mỗi lần render ra kết quả khác nhau.
Cách fix: đặt code phụ thuộc browser vào useEffect vì nó chỉ chạy trên client, dùng dynamic import với option ssr: false cho component cần browser APIs, hoặc thêm suppressHydrationWarning cho trường hợp biết trước sẽ khác nhau như timestamp.
Nguyên tắc quan trọng là HTML server trả về phải khớp chính xác với kết quả initial render trên client.
useCallback dùng để giữ nguyên reference của một function qua các lần render, thường dùng khi truyền callback xuống child component đã được bọc React.memo() để tránh re-render không cần thiết.
useMemo dùng để cache lại giá trị tính toán nặng, ví dụ như filtered list hoặc sorted data, chỉ tính lại khi dependency thay đổi. Trong dự án thực tế, useCallback hay dùng cho handleClick hoặc onChange prop, còn useMemo dùng cho danh sách đã lọc hoặc tính toán phức tạp trong component lớn. Lưu ý không nên lạm dụng vì bản thân memoization cũng có chi phí — chỉ dùng khi thực sự cần tối ưu.
React cảnh báo 'missing dependency' khi bạn sử dụng một biến bên trong useEffect nhưng không khai báo nó trong dependency array, vì lúc đó effect sẽ dùng giá trị cũ do stale closure.
Nếu không truyền dependency array, effect sẽ chạy sau mỗi lần render. Nếu truyền mảng rỗng [], effect chỉ chạy một lần sau khi component mount. Khi thêm biến vào mảng, effect sẽ chạy lại mỗi khi biến đó thay đổi — đây là cách đúng để đồng bộ side effect với state hoặc props.
Race condition xảy ra khi nhiều request bất đồng bộ được gửi đi và response trả về không đúng thứ tự, khiến UI hiển thị dữ liệu sai.
Cách xử lý phổ biến nhất là dùng AbortController trong cleanup function của useEffect: tạo controller, truyền signal vào fetch, và return hàm abort trong cleanup.
const controller = new AbortController();
fetch(url, { signal: controller.signal });
return () => controller.abort();Ngoài ra có thể dùng thư viện như TanStack Query vì nó tự động xử lý caching, deduplication và cancellation.
Controlled component là component mà giá trị input được quản lý bởi React state thông qua value và onChange — cho phép kiểm soát hoàn toàn dữ liệu và validate real-time. Uncontrolled component dùng ref để lấy giá trị trực tiếp từ DOM khi cần, ví dụ lúc submit form — đơn giản hơn nhưng ít kiểm soát hơn.
Trong thực tế, controlled component phù hợp cho form phức tạp cần validate, format hoặc hiển thị điều kiện. Uncontrolled component phù hợp cho form đơn giản hoặc khi tích hợp với thư viện bên ngoài không hỗ trợ React state.
Prop drilling là tình trạng phải truyền props qua nhiều tầng component trung gian, dù những component đó không thực sự sử dụng props đó — chỉ đóng vai trò chuyển tiếp.
Điều này làm code khó bảo trì và khó refactor.
Giải pháp phổ biến gồm:
- Context API cho state toàn cục đơn giản như theme hoặc user info,
- Redux hoặc Zustand cho state phức tạp cần nhiều nơi truy cập,
- composition pattern — truyền JSX làm children thay vì truyền data qua props
Nên chọn giải pháp phù hợp với quy mô dự án, không nên dùng Redux cho mọi trường hợp.
Key prop giúp React nhận diện phần tử nào đã thay đổi, được thêm mới, hoặc bị xóa trong danh sách để cập nhật DOM hiệu quả thông qua thuật toán reconciliation.
- Dùng index làm key sẽ gây lỗi nghiêm trọng khi danh sách bị sắp xếp lại hoặc xóa phần tử, vì index sẽ gán nhầm cho item khác khiến state và input bị lẫn lộn.
Ví dụ nếu xóa phần tử đầu tiên, tất cả index sẽ dịch lên và React nghĩ chỉ phần tử cuối bị xóa.
- Luôn dùng unique ID từ dữ liệu (database ID, UUID) làm key để đảm bảo chính xác.
Lazy loading là kỹ thuật chỉ tải component khi thực sự cần thiết, giúp giảm kích thước bundle ban đầu và tăng tốc trang.
- Cách implement: dùng React.lazy() kết hợp Suspense, ví dụ const Dashboard = lazy(() => import('./Dashboard')) rồi bọc trong <Suspense fallback={<Loading />}>.
- Thường áp dụng cho route component, modal nặng, hoặc tab ít được xem.
- Đánh đổi là người dùng sẽ thấy loading delay khi truy cập lần đầu, nhưng bù lại initial load nhanh hơn đáng kể.
Virtual scrolling là kỹ thuật chỉ render những item đang hiển thị trong viewport, còn những item ngoài tầm nhìn sẽ bị loại khỏi DOM.
- Nhờ vậy dù danh sách có hàng chục ngàn item, trình duyệt chỉ cần xử lý vài chục DOM node tại một thời điểm, tiết kiệm bộ nhớ và giữ hiệu năng mượt mà.
- Các thư viện phổ biến để implement gồm react-window và @tanstack/react-virtual.
- Đánh đổi là code phức tạp hơn, khó styling các item có chiều cao động, và cần tính toán vị trí scroll chính xác.
React.memo() là higher-order component bọc quanh functional component để bỏ qua re-render nếu props không thay đổi, so sánh bằng shallow comparison. Nên dùng khi: child component có chi phí render cao, parent re-render thường xuyên mà props của child ít thay đổi.
Cần lưu ý rằng shallow comparison chỉ so sánh tham chiếu, nên nếu truyền object hoặc function mới mỗi lần render thì memo sẽ vô hiệu — vì vậy nên kết hợp với useCallback và useMemo. Không nên dùng memo cho mọi component vì bản thân việc so sánh props cũng tốn chi phí.