React interviews test component thinking, hooks mastery, performance optimisation, and architectural knowledge. This guide covers the 50 most common questions — with concise answers and runnable code examples.
Quick reference
| Topic | Most asked questions |
|---|---|
| Fundamentals | Virtual DOM, JSX, props vs state |
| Hooks | useState, useEffect, useRef, custom hooks |
| Performance | memo, useMemo, useCallback, lazy loading |
| Patterns | Lifting state, composition, render props, HOC |
| Advanced | Context, useReducer, reconciliation, concurrent |
Fundamentals
1. What is the Virtual DOM and how does it work?
The Virtual DOM is an in-memory representation of the real DOM. React:
- Renders a virtual tree in memory
- Diffs the new tree against the previous one (reconciliation)
- Calculates the minimal set of DOM mutations
- Applies only those changes to the real DOM (commit phase)
// React calculates what changed and only updates <span>, not the whole list
function Counter() {
const [count, setCount] = React.useState(0);
return (
<ul>
<li>Static item</li>
<li>Count: <span>{count}</span></li>
</ul>
);
}
This batches DOM writes and avoids expensive reflows for unchanged nodes.
2. What is JSX? Is it required?
JSX is syntactic sugar for React.createElement calls. It is not required but is used in almost every React codebase.
// JSX
const el = <h1 className="title">Hello</h1>;
// Compiles to (React 17- classic transform)
const el = React.createElement('h1', { className: 'title' }, 'Hello');
// React 17+ automatic transform — no import needed
3. What is the difference between props and state?
| Props | State | |
|---|---|---|
| Owner | Parent passes them | Component owns them |
| Mutable? | Read-only in child | Updated via setter |
| Re-render | When parent re-renders | When setState is called |
| Usage | Configuration / data down | Internal interactive data |
// props — parent-controlled
function Greeting({ name }) {
return <p>Hello, {name}</p>;
}
// state — component-controlled
function Toggle() {
const [on, setOn] = React.useState(false);
return <button onClick={() => setOn(v => !v)}>{on ? 'ON' : 'OFF'}</button>;
}
4. What are controlled vs uncontrolled components?
Controlled: React state is the single source of truth for the input value.
function ControlledInput() {
const [value, setValue] = React.useState('');
return <input value={value} onChange={e => setValue(e.target.value)} />;
}
Uncontrolled: The DOM owns the value; you read it via a ref.
function UncontrolledInput() {
const ref = React.useRef();
const handleSubmit = () => console.log(ref.current.value);
return <input ref={ref} defaultValue="" />;
}
Prefer controlled components — they make validation and derived state straightforward.
5. What is reconciliation?
Reconciliation is how React decides which DOM nodes to update. React uses two heuristics:
- Different element types → tear down the old tree, build a new one
- Same element type → update only changed attributes
Keys tell React which list items match across renders:
// Bad — React can't match items after reorder
items.map(item => <li>{item.name}</li>)
// Good — stable key lets React reuse DOM nodes
items.map(item => <li key={item.id}>{item.name}</li>)
6. What is the difference between class components and function components?
| Class | Function | |
|---|---|---|
| State | this.state / setState |
useState |
| Lifecycle | componentDidMount etc. |
useEffect |
| Code reuse | HOC / render props | Custom hooks |
this |
Required | Not used |
| Status | Legacy (still works) | Recommended |
// Class
class Counter extends React.Component {
state = { count: 0 };
render() {
return <button onClick={() => this.setState(s => ({ count: s.count + 1 }))}>
{this.state.count}
</button>;
}
}
// Function (preferred)
function Counter() {
const [count, setCount] = React.useState(0);
return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
}
Hooks
7. What rules must hooks follow?
- Only call at the top level — not inside loops, conditions, or nested functions
- Only call from React functions — function components or custom hooks
// ❌ Wrong — conditional hook call
function Bad({ show }) {
if (show) {
const [x, setX] = useState(0); // breaks hook order
}
}
// ✅ Correct
function Good({ show }) {
const [x, setX] = useState(0);
if (!show) return null;
// …
}
8. Explain useState — when does a re-render happen?
useState returns [value, setter]. React schedules a re-render when the setter is called with a different value (using Object.is comparison).
const [count, setCount] = useState(0);
setCount(1); // re-render (0 !== 1)
setCount(0); // no re-render (0 === 0)
setCount(c => c + 1); // functional update — safe when depending on prev value
Object state: React compares by reference, so always return a new object:
// ❌ mutation — same reference, no re-render
setUser(user => { user.name = 'Alice'; return user; });
// ✅ new object
setUser(user => ({ ...user, name: 'Alice' }));
9. Explain useEffect and its dependency array
useEffect(fn, deps) runs fn after the render where deps changed.
| deps | Runs |
|---|---|
| Omitted | After every render |
[] |
Once after mount |
[a, b] |
After mount + whenever a or b changes |
useEffect(() => {
const sub = subscribe(id);
return () => sub.unsubscribe(); // cleanup on unmount or id change
}, [id]);
Common pitfall — missing a dependency causes stale closures:
// ❌ count is stale after first render
useEffect(() => {
const id = setInterval(() => console.log(count), 1000);
return () => clearInterval(id);
}, []); // count not in deps
// ✅ add count to deps
}, [count]);
10. What is useRef and when should you use it?
useRef returns a mutable { current } object that persists across renders without causing re-renders.
Use cases:
// 1. DOM access
const inputRef = useRef(null);
<input ref={inputRef} />
// inputRef.current.focus()
// 2. Mutable value that shouldn't trigger re-render (e.g. timer ID)
const timerId = useRef(null);
timerId.current = setTimeout(...);
// 3. Track previous value
function usePrevious(value) {
const ref = useRef();
useEffect(() => { ref.current = value; });
return ref.current; // previous render's value
}
11. What is useContext and when should you avoid it?
useContext subscribes a component to a context value. Every consumer re-renders when the context value changes.
const ThemeContext = React.createContext('light');
function App() {
return (
<ThemeContext.Provider value="dark">
<Button />
</ThemeContext.Provider>
);
}
function Button() {
const theme = useContext(ThemeContext); // 'dark'
return <button className={theme}>Click</button>;
}
Avoid context for frequently-changing values (e.g. mouse position) — it re-renders all consumers. Prefer Zustand, Jotai, or Redux for high-frequency state.
12. What is useReducer and when is it better than useState?
useReducer(reducer, initialState) is useState with explicit actions — better when:
- State has multiple sub-values that update together
- Next state depends on previous state in a complex way
- You want to centralise update logic
const initialState = { count: 0, step: 1 };
function reducer(state, action) {
switch (action.type) {
case 'increment': return { ...state, count: state.count + state.step };
case 'setStep': return { ...state, step: action.payload };
default: throw new Error('Unknown action');
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
<p>{state.count}</p>
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
<input
type="number"
value={state.step}
onChange={e => dispatch({ type: 'setStep', payload: +e.target.value })}
/>
</>
);
}
13. What is useMemo?
useMemo memoises the return value of a function. Re-runs only when dependencies change.
const sortedList = useMemo(
() => [...items].sort((a, b) => a.name.localeCompare(b.name)),
[items]
);
Use when the computation is genuinely expensive. Don't useMemo everything — the comparison overhead costs too.
14. What is useCallback?
useCallback memoises a function reference. Useful to prevent child re-renders when passing callbacks as props.
// Without useCallback — new function reference on every parent render
// → Child re-renders even when nothing changed
const handleDelete = useCallback(
(id) => setItems(items => items.filter(item => item.id !== id)),
[] // stable reference; functional update so items not needed as dep
);
<ExpensiveChild onDelete={handleDelete} />
15. What are custom hooks?
A custom hook is a function starting with use that calls other hooks. It extracts and reuses stateful logic without sharing component state.
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
let cancelled = false;
fetch(url)
.then(r => r.json())
.then(d => { if (!cancelled) setData(d); })
.catch(e => { if (!cancelled) setError(e); })
.finally(() => { if (!cancelled) setLoading(false); });
return () => { cancelled = true; };
}, [url]);
return { data, loading, error };
}
// Usage
const { data, loading } = useFetch('/api/users');
Performance
16. What is React.memo?
React.memo is a HOC that skips re-rendering a function component if its props are shallowly equal to the previous render.
const Avatar = React.memo(function Avatar({ name, src }) {
return <img src={src} alt={name} />;
});
// Only re-renders when name or src actually changes
Custom comparison:
const Avatar = React.memo(Avatar, (prev, next) => prev.id === next.id);
17. Explain reconciliation keys and why index keys are bad
Using array indexes as keys causes bugs when the list order changes:
// ❌ index key — React reuses wrong DOM nodes after sort/insert
items.map((item, i) => <Row key={i} data={item} />)
// ✅ stable unique ID
items.map(item => <Row key={item.id} data={item} />)
When the list is reordered, index keys trick React into thinking existing components only changed their props — causing state (e.g. input values) to persist on the wrong item.
18. What is lazy loading and Suspense?
React.lazy defers loading a component until it's first rendered. Suspense shows a fallback while loading.
const HeavyChart = React.lazy(() => import('./HeavyChart'));
function Dashboard() {
return (
<Suspense fallback={<p>Loading chart…</p>}>
<HeavyChart />
</Suspense>
);
}
This splits the bundle so the initial JS is smaller.
19. What causes unnecessary re-renders?
- Parent re-renders → all children re-render by default
- Context value changes → all consumers re-render
- New object/function reference passed as prop on every render
useStatesetter called with same value (batched, no render in practice since React 18)
Fixes: React.memo, useMemo, useCallback, stable context values, splitting context.
20. What is the React Profiler?
The React DevTools Profiler records which components rendered and how long each took. Use it to find hot paths before optimising — don't guess.
// Programmatic profiling
<Profiler id="Chart" onRender={(id, phase, duration) => {
console.log(`${id} ${phase}: ${duration}ms`);
}}>
<HeavyChart />
</Profiler>
Component patterns
21. What is prop drilling and how do you avoid it?
Prop drilling is passing props through many intermediate components that don't use them.
Solutions:
- Context — for genuinely global data (theme, user, locale)
- State management library — Zustand, Jotai, Redux
- Component composition — pass components as children instead of data
// Composition avoids drilling — parent renders children directly
function Layout({ sidebar, content }) {
return (
<div className="layout">
<aside>{sidebar}</aside>
<main>{content}</main>
</div>
);
}
<Layout
sidebar={<Nav user={user} />}
content={<Feed posts={posts} />}
/>
22. What is lifting state up?
When two sibling components need to share state, move that state to their closest common ancestor and pass it down via props.
function Parent() {
const [query, setQuery] = useState('');
return (
<>
<SearchInput value={query} onChange={setQuery} />
<ResultsList query={query} />
</>
);
}
23. What are Higher-Order Components (HOC)?
A HOC is a function that takes a component and returns a new component with added behaviour.
function withAuth(WrappedComponent) {
return function AuthGuard(props) {
const { user } = useAuth();
if (!user) return <Redirect to="/login" />;
return <WrappedComponent {...props} />;
};
}
const ProtectedDashboard = withAuth(Dashboard);
Custom hooks cover most HOC use cases today, but HOCs are still used in some libraries (Redux connect, React Router withRouter).
24. What is the render props pattern?
Passing a function as a prop to share rendering logic.
function MouseTracker({ render }) {
const [pos, setPos] = useState({ x: 0, y: 0 });
return (
<div onMouseMove={e => setPos({ x: e.clientX, y: e.clientY })}>
{render(pos)}
</div>
);
}
<MouseTracker render={pos => <p>Mouse at {pos.x}, {pos.y}</p>} />
Custom hooks largely replaced this pattern.
25. What is the children prop?
props.children is anything nested between a component's opening and closing tags.
function Card({ title, children }) {
return (
<div className="card">
<h2>{title}</h2>
{children}
</div>
);
}
<Card title="Welcome">
<p>Content here</p>
<button>Action</button>
</Card>
State management
26. When should you use a global state library vs Context?
| Scenario | Solution |
|---|---|
| Theme / locale / current user | Context (rarely changes) |
| Frequently updated shared state | Zustand / Jotai |
| Complex async flows, devtools | Redux Toolkit |
| Server state (caching, refetch) | TanStack Query / SWR |
27. What is TanStack Query (React Query) used for?
It manages server state — fetching, caching, background refetching, and synchronisation — so you don't have to build that yourself.
const { data, isLoading, error } = useQuery({
queryKey: ['user', userId],
queryFn: () => fetch(`/api/users/${userId}`).then(r => r.json()),
staleTime: 60_000, // treat as fresh for 60s
});
Compare to manual useEffect fetching: React Query gives you caching, deduplication, refetch on focus, optimistic updates, and more for free.
28. What is Zustand?
A minimal, unopinionated state management library with a simple store API.
import { create } from 'zustand';
const useStore = create(set => ({
count: 0,
increment: () => set(state => ({ count: state.count + 1 })),
}));
function Counter() {
const { count, increment } = useStore();
return <button onClick={increment}>{count}</button>;
}
Forms
29. How do you handle forms in React?
Option 1: Controlled (vanilla)
function Form() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const handleSubmit = e => {
e.preventDefault();
loginUser({ email, password });
};
return (
<form onSubmit={handleSubmit}>
<input value={email} onChange={e => setEmail(e.target.value)} type="email" />
<input value={password} onChange={e => setPassword(e.target.value)} type="password" />
<button type="submit">Login</button>
</form>
);
}
Option 2: React Hook Form (preferred for complex forms)
import { useForm } from 'react-hook-form';
function Form() {
const { register, handleSubmit, formState: { errors } } = useForm();
return (
<form onSubmit={handleSubmit(data => console.log(data))}>
<input {...register('email', { required: true, pattern: /\S+@\S+\.\S+/ })} />
{errors.email && <span>Invalid email</span>}
<button type="submit">Submit</button>
</form>
);
}
Routing
30. How does React Router v6 work?
import { BrowserRouter, Routes, Route, Link, useParams, useNavigate } from 'react-router-dom';
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/users/:id" element={<UserPage />} />
<Route path="*" element={<NotFound />} />
</Routes>
</BrowserRouter>
);
}
function UserPage() {
const { id } = useParams();
const navigate = useNavigate();
return <button onClick={() => navigate('/')}>Back</button>;
}
Advanced React
31. What is the React Fiber architecture?
Fiber (introduced in React 16) is a complete rewrite of the reconciliation engine. Key improvements:
- Incremental rendering: break work into units, pause and resume
- Priority scheduling: urgent updates (user input) can interrupt lower-priority work
- Concurrency: foundation for Concurrent Mode, Suspense, and transitions
32. What is Concurrent React?
Concurrent React (React 18+) allows React to prepare multiple UI versions simultaneously without blocking the main thread.
Key APIs:
import { startTransition, useTransition, useDeferredValue } from 'react';
// Mark an update as non-urgent
startTransition(() => setSearchQuery(value));
// Show pending indicator
const [isPending, startTransition] = useTransition();
// Defer a derived value during fast typing
const deferredQuery = useDeferredValue(searchQuery);
33. What is useTransition?
It marks state updates as non-urgent so React can keep the UI responsive during them.
function SearchPage() {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
const [isPending, startTransition] = useTransition();
function handleChange(e) {
const value = e.target.value;
setQuery(value); // urgent: update input immediately
startTransition(() => {
setResults(expensiveSearch(value)); // non-urgent: can be interrupted
});
}
return (
<>
<input value={query} onChange={handleChange} />
{isPending ? <Spinner /> : <ResultList data={results} />}
</>
);
}
34. What are Error Boundaries?
Error boundaries catch JavaScript errors in their child tree and display a fallback UI instead of crashing the app. They must be class components.
class ErrorBoundary extends React.Component {
state = { hasError: false };
static getDerivedStateFromError() {
return { hasError: true };
}
componentDidCatch(error, info) {
logErrorToService(error, info.componentStack);
}
render() {
if (this.state.hasError) return <h2>Something went wrong.</h2>;
return this.props.children;
}
}
<ErrorBoundary>
<SuspiciousComponent />
</ErrorBoundary>
35. What is forwardRef?
forwardRef lets a component pass a ref down to a DOM element inside it.
const FancyInput = React.forwardRef(function FancyInput(props, ref) {
return <input ref={ref} className="fancy" {...props} />;
});
// Parent can now focus the inner input
const ref = useRef();
<FancyInput ref={ref} />
ref.current.focus();
36. What is useImperativeHandle?
Used with forwardRef to expose a custom imperative API instead of the raw DOM node.
const VideoPlayer = forwardRef(function VideoPlayer(props, ref) {
const videoRef = useRef();
useImperativeHandle(ref, () => ({
play: () => videoRef.current.play(),
pause: () => videoRef.current.pause(),
}));
return <video ref={videoRef} {...props} />;
});
// Parent uses clean API
playerRef.current.play();
37. What are portals?
Portals render children into a different DOM node — useful for modals and tooltips that need to escape overflow/z-index constraints.
import { createPortal } from 'react-dom';
function Modal({ children }) {
return createPortal(
<div className="modal-overlay">{children}</div>,
document.getElementById('modal-root')
);
}
The component is still in the React tree (events bubble normally) but renders outside its DOM parent.
38. What are Server Components?
React Server Components (RSC) run only on the server — they have zero JS bundle cost and can directly access databases, files, and secrets.
// app/users/page.tsx (Next.js App Router — Server Component by default)
async function UsersPage() {
const users = await db.user.findMany(); // direct DB access, not exposed to client
return <ul>{users.map(u => <li key={u.id}>{u.name}</li>)}</ul>;
}
Client Components are opted in with 'use client'. Server Components cannot use hooks or browser APIs.
39. Explain the React render lifecycle (class components)
Mount: constructor → render → DOM update → componentDidMount
Update: shouldComponentUpdate → render → DOM update → componentDidUpdate
Unmount: componentWillUnmount
For function components, useEffect maps roughly to:
[] → componentDidMount + componentWillUnmount (cleanup)
[dep] → componentDidMount + componentDidUpdate (when dep changes) + cleanup
40. What is StrictMode?
React.StrictMode wraps your app in development to:
- Double-invoke render functions to detect side effects in render
- Double-invoke effects to catch missing cleanup
- Warn on deprecated APIs
<React.StrictMode>
<App />
</React.StrictMode>
No effect in production builds.
TypeScript with React
41. How do you type component props in TypeScript?
interface ButtonProps {
label: string;
onClick: () => void;
variant?: 'primary' | 'secondary';
disabled?: boolean;
}
function Button({ label, onClick, variant = 'primary', disabled = false }: ButtonProps) {
return <button onClick={onClick} disabled={disabled} className={variant}>{label}</button>;
}
42. How do you type events in React TypeScript?
// Input change
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setValue(e.target.value);
};
// Form submit
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
};
// Button click
const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
e.preventDefault();
};
43. How do you type useState in TypeScript?
TypeScript infers the type from the initial value. Use a generic when the type is wider than the initial value.
const [count, setCount] = useState(0); // number inferred
const [user, setUser] = useState<User | null>(null); // explicit generic
const [items, setItems] = useState<string[]>([]); // array
44. How do you type a custom hook?
interface FetchResult<T> {
data: T | null;
loading: boolean;
error: Error | null;
}
function useFetch<T>(url: string): FetchResult<T> {
const [data, setData] = useState<T | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
fetch(url)
.then(r => r.json() as Promise<T>)
.then(setData)
.catch(setError)
.finally(() => setLoading(false));
}, [url]);
return { data, loading, error };
}
Testing
45. How do you test React components?
The standard tool is React Testing Library (RTL), which tests from the user's perspective.
import { render, screen, fireEvent } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
test('increments counter on click', async () => {
render(<Counter />);
const button = screen.getByRole('button', { name: /increment/i });
await userEvent.click(button);
expect(screen.getByText('1')).toBeInTheDocument();
});
46. What is the difference between getBy, queryBy, and findBy in RTL?
| Query | When found | When not found | Async? |
|---|---|---|---|
getBy* |
Element | Throws | No |
queryBy* |
Element | Returns null |
No |
findBy* |
Element | Rejects (timeout) | Yes |
// Assert element doesn't exist — use queryBy
expect(screen.queryByText('Loading…')).not.toBeInTheDocument();
// Wait for element to appear — use findBy
const heading = await screen.findByRole('heading');
Common interview gotchas
47. Why shouldn't you call hooks conditionally?
React maintains hook state by call order. If you skip a hook call (e.g. inside an if), all subsequent hooks get the wrong state.
// ❌ Breaks hook order on re-render
if (user) {
const [name, setName] = useState(user.name); // hook 1 in some renders, hook 2 in others
}
const [count, setCount] = useState(0);
// ✅ Always call, conditionally use
const [name, setName] = useState(user?.name ?? '');
48. What is a stale closure in React?
A stale closure is when a callback or effect captures a value from an old render and never gets the updated value.
// ❌ Stale closure — count is always 0 inside the interval
useEffect(() => {
const id = setInterval(() => console.log(count), 1000);
return () => clearInterval(id);
}, []); // count not in deps
// ✅ Fix 1: add to deps (re-creates interval on each count change)
}, [count]);
// ✅ Fix 2: use a ref to always read latest value
const countRef = useRef(count);
countRef.current = count;
useEffect(() => {
const id = setInterval(() => console.log(countRef.current), 1000);
return () => clearInterval(id);
}, []);
49. Why does 0 && <Component /> render 0?
Logical AND (&&) returns the first falsy value. 0 is falsy but React renders numbers.
// ❌ Renders literal "0" when count is 0
{count && <Badge count={count} />}
// ✅ Use explicit boolean
{count > 0 && <Badge count={count} />}
{!!count && <Badge count={count} />}
{count ? <Badge count={count} /> : null}
50. What is the difference between useEffect and useLayoutEffect?
useEffect |
useLayoutEffect |
|
|---|---|---|
| When | After browser paint | After DOM update, before paint |
| Thread | Asynchronous | Synchronous (blocks paint) |
| Use for | Data fetching, subscriptions | DOM measurements, animations |
// Measure element after DOM update, before user sees flicker
useLayoutEffect(() => {
const { width } = ref.current.getBoundingClientRect();
setWidth(width);
}, []);
Prefer useEffect — useLayoutEffect blocks paint and can degrade perceived performance.
Common mistakes
| Mistake | Problem | Fix |
|---|---|---|
| Using index as key | Incorrect state on reorder | Use stable unique ID |
| Mutating state directly | No re-render | Return new object/array |
Missing useEffect deps |
Stale closure bugs | Add all referenced values |
0 && in JSX |
Renders literal 0 |
Use count > 0 && or ternary |
| Calling hooks conditionally | Hook order breaks | Always call hooks at top level |
useEffect for derived state |
Double render | Compute value inline / use useMemo |
| Over-memoising everything | Hidden bugs, harder to debug | Profile first, memo only hot paths |
| Setting state in render | Infinite loop | Use useEffect or compute inline |
FAQ
Q: When should I use useEffect vs event handlers for side effects?
A: Prefer event handlers (onClick, onSubmit) for user-initiated side effects. Reserve useEffect for effects caused by rendering itself — syncing with external systems, subscriptions, timers.
Q: What's the difference between React.memo and useMemo?
A: React.memo wraps a component to skip re-renders when props are unchanged. useMemo memoises a value inside a component.
Q: Is useCallback always necessary when passing functions to children?
A: Only when the child is wrapped in React.memo. Otherwise the function reference instability doesn't cause extra renders. Measure with the Profiler before adding useCallback.
Q: How does React batch state updates?
A: React 18 batches all state updates automatically — inside event handlers, setTimeout, fetch callbacks, and anywhere else. Before React 18, only event handlers were batched.
Q: What is the difference between key on a list item vs on a component to force reset?
A: key on a list item helps React match items across renders. But you can also use key on a non-list component to completely reset its state — changing key unmounts and remounts the component.
Q: Should I use class components in 2024?
A: No, unless you need Error Boundaries (must be class) or you're maintaining legacy code. All new development should use function components and hooks.