Skip to content

Practical React JS Interview Questions

46 questions with answers. Hands-on questions where you write code, a query, a formula or concrete steps.

How to approach these questions

Talk while you work: say your approach first, write the simplest correct version, then improve it and mention edge cases.

Solved — practicalStart practicing
3 QUESTIONS FREESign up to unlock the full question bank and track your progress.Unlock every question and answer, then continue practising company-wise and package-wise.
3 questions free Unlock the rest in seconds
01

How do you return multiple elements from a component without adding an extra div?

Save to ▾
Show answer
Use a Fragment. The short syntax <>...</> groups children without adding a DOM node. Use the long form when you need a key, for example inside a map. function Row({ item }) { return ( <> <td>{item.name}</td> <td>{item.price}</td> </> ); } items.map(i => <React.Fragment key={i.id}>...</React.Fragment>)
02

Show the common ways to do conditional rendering in React.

Save to ▾
Show answer
You can use if statements before return, the ternary operator, or logical AND. Be careful with && and numbers, because 0 is rendered. function Status({ user, count }) { if (!user) return <Login />; return ( <div> {user.isAdmin ? <AdminPanel /> : <Dashboard />} {count > 0 && <Badge count={count} />} </div> ); }
03

How do you create an error boundary in React?

Save to ▾
Show answer
An error boundary catches errors thrown while rendering its children and shows a fallback UI. It must be a class component (or use the react-error-boundary package). class ErrorBoundary extends React.Component { state = { hasError: false }; static getDerivedStateFromError() { return { hasError: true }; } componentDidCatch(error, info) { logError(error, info.componentStack); } render() { return this.state.hasError ? <p>Something went wrong</p> : this.props.children; } } It does not catch errors in event handlers or async code.
04

What are portals and when would you use them?

Save to ▾
Show answer
A portal renders children into a DOM node outside the parent component's DOM hierarchy while keeping them in the same React tree, so context and event bubbling still work. import { createPortal } from 'react-dom'; function Modal({ children }) { return createPortal( <div className="modal">{children}</div>, document.getElementById('modal-root') ); } They are used for modals, tooltips and dropdowns that must escape overflow hidden or z-index issues.
05

Write a simple counter component with increment, decrement and reset buttons.

Save to ▾
Show answer
A counter is the classic useState example. Use the functional updater so each click works on the latest value. import { useState } from 'react'; export default function Counter() { const [count, setCount] = useState(0); return ( <div> <h2>{count}</h2> <button onClick={() => setCount(c => c + 1)}>+</button> <button onClick={() => setCount(c => c - 1)}>-</button> <button onClick={() => setCount(0)}>Reset</button> </div> ); }
06

Why should you use the functional form of setState? Show an example.

Save to ▾
Show answer
State updates are scheduled, and each render sees a snapshot of state. If you call setCount(count + 1) several times in one handler, all calls read the same old value. The updater function receives the latest pending value. function addThree() { setCount(c => c + 1); setCount(c => c + 1); setCount(c => c + 1); } // count increases by 3, not 1
07

How do you update objects and arrays in state without mutating them?

Save to ▾
Show answer
Always create new copies so React detects the change by reference. // object setUser(u => ({ ...u, city: 'Pune' })); // add to array setItems(list => [...list, newItem]); // remove setItems(list => list.filter(i => i.id !== id)); // update one item setItems(list => list.map(i => i.id === id ? { ...i, done: true } : i));
08

How do you give default values to props in a function component?

Save to ▾
Show answer
Use JavaScript default parameters while destructuring props. defaultProps for function components was removed in React 19, so this is the standard way. function Button({ label = 'Submit', variant = 'primary', onClick }) { return ( <button className={`btn btn-${variant}`} onClick={onClick}> {label} </button> ); }
09

How do you type props for a component in TypeScript?

Save to ▾
Show answer
Define a type or interface and annotate the destructured props. type ProductCardProps = { title: string; price: number; tags?: string[]; onAdd: (id: string) => void; children?: React.ReactNode; }; function ProductCard({ title, price, tags = [], onAdd, children }: ProductCardProps) { return <div onClick={() => onAdd(title)}>{title} - Rs {price}{children}</div>; }
10

How does a child component send data back to its parent?

Save to ▾
Show answer
The parent passes a callback function as a prop, and the child calls it with the data. function Parent() { const [city, setCity] = useState(''); return <CityPicker onSelect={setCity} />; } function CityPicker({ onSelect }) { return ( <select onChange={e => onSelect(e.target.value)}> <option>Delhi</option> <option>Noida</option> </select> ); }
11

How would you update deeply nested state cleanly? Show with Immer.

Save to ▾
Show answer
Manual spreading of nested objects is error prone. Immer lets you write mutating code on a draft and produces an immutable copy. Redux Toolkit uses it internally. import { useImmer } from 'use-immer'; const [form, updateForm] = useImmer({ address: { city: '', pin: '' } }); updateForm(draft => { draft.address.city = 'Noida'; }); Also consider flattening state to avoid deep nesting.
12

What is lazy initialisation in useState and when should you use it?

Save to ▾
Show answer
If the initial value is expensive to compute, pass a function instead of the value. React calls it only on the first render. // runs on every render (wasteful) const [data, setData] = useState(JSON.parse(localStorage.getItem('cart'))); // runs once const [data, setData] = useState(() => JSON.parse(localStorage.getItem('cart') ?? '[]') );
13

When should you choose useReducer over useState? Show a small example.

Save to ▾
Show answer
Use useReducer when state has several related fields or many update types, so logic lives in one pure, testable function. function reducer(state, action) { switch (action.type) { case 'added': return [...state, action.item]; case 'removed': return state.filter(i => i.id !== action.id); default: return state; } } const [cart, dispatch] = useReducer(reducer, []); dispatch({ type: 'added', item });
14

Write a custom hook to fetch data with loading and error states.

Save to ▾
Show answer
A custom hook is a function starting with use that combines other hooks. function useFetch(url) { const [state, setState] = useState({ data: null, loading: true, error: null }); useEffect(() => { const ctrl = new AbortController(); setState(s => ({ ...s, loading: true })); fetch(url, { signal: ctrl.signal }) .then(r => { if (!r.ok) throw new Error(r.status); return r.json(); }) .then(data => setState({ data, loading: false, error: null })) .catch(err => { if (err.name !== 'AbortError') setState({ data: null, loading: false, error: err }); }); return () => ctrl.abort(); }, [url]); return state; }
15

How do you expose a custom method like focus() from a child to a parent in React 19?

Save to ▾
Show answer
In React 19, function components receive ref as a normal prop, so forwardRef is no longer needed. Use useImperativeHandle to expose only selected methods. function SearchInput({ ref }) { const inputRef = useRef(null); useImperativeHandle(ref, () => ({ focus: () => inputRef.current.focus(), clear: () => { inputRef.current.value = ''; } })); return <input ref={inputRef} />; } // parent: const r = useRef(); <SearchInput ref={r} />; r.current.focus();
16

Write a useDebounce custom hook.

Save to ▾
Show answer
Debouncing delays a value until the user stops typing, which reduces API calls. function useDebounce(value, delay = 400) { const [debounced, setDebounced] = useState(value); useEffect(() => { const t = setTimeout(() => setDebounced(value), delay); return () => clearTimeout(t); }, [value, delay]); return debounced; } const q = useDebounce(searchText); // fetch when q changes
17

Create a Redux Toolkit slice for a counter.

Save to ▾
Show answer
createSlice generates action creators and the reducer. Immer lets you write mutating syntax safely. import { createSlice } from '@reduxjs/toolkit'; const counterSlice = createSlice({ name: 'counter', initialState: { value: 0 }, reducers: { increment: state => { state.value += 1; }, addBy: (state, action) => { state.value += action.payload; } } }); export const { increment, addBy } = counterSlice.actions; export default counterSlice.reducer;
18

How do you set up a Redux store and use it in components?

Save to ▾
Show answer
Use configureStore, wrap the app with Provider and use hooks. const store = configureStore({ reducer: { counter: counterReducer } }); createRoot(el).render( <Provider store={store}><App /></Provider> ); function Counter() { const value = useSelector(state => state.counter.value); const dispatch = useDispatch(); return <button onClick={() => dispatch(increment())}>{value}</button>; }
19

How do you handle async API calls with createAsyncThunk?

Save to ▾
Show answer
createAsyncThunk creates pending, fulfilled and rejected actions around an async function, and you handle them in extraReducers. export const fetchOrders = createAsyncThunk('orders/fetch', async () => { const res = await fetch('/api/orders'); return res.json(); }); extraReducers: builder => { builder .addCase(fetchOrders.pending, s => { s.status = 'loading'; }) .addCase(fetchOrders.fulfilled, (s, a) => { s.status = 'idle'; s.list = a.payload; }) .addCase(fetchOrders.rejected, (s, a) => { s.status = 'failed'; s.error = a.error.message; }); }
20

Show how to fetch data with TanStack Query.

Save to ▾
Show answer
TanStack Query caches by query key and handles loading, errors, retries and background refetching. const queryClient = new QueryClient(); // wrap app: <QueryClientProvider client={queryClient}> function Orders({ userId }) { const { data, isPending, error } = useQuery({ queryKey: ['orders', userId], queryFn: () => fetch(`/api/users/${userId}/orders`).then(r => r.json()), staleTime: 60_000 }); if (isPending) return <Spinner />; if (error) return <p>Error</p>; return <OrderList orders={data} />; }
21

What is Zustand and how do you use it?

Save to ▾
Show answer
Zustand is a small state library with a hook-based API and no Provider. import { create } from 'zustand'; const useCart = create(set => ({ items: [], add: item => set(s => ({ items: [...s.items, item] })), clear: () => set({ items: [] }) })); const count = useCart(s => s.items.length); Components subscribe with selectors, so they re-render only when the selected slice changes.
22

How do you debug Redux state changes?

Save to ▾
Show answer
Install the Redux DevTools browser extension; configureStore enables it automatically in development. You can see every dispatched action, the state before and after, the diff, and jump back in time to earlier states. Check whether the expected action was dispatched, whether its payload is correct and whether the reducer updated the right slice. For selectors, log their output or use the React DevTools profiler.
23

How do you persist part of your state, like the cart, across page reloads?

Save to ▾
Show answer
Save it to localStorage and read it on startup. With Zustand, the persist middleware does this. import { persist } from 'zustand/middleware'; const useCart = create(persist( set => ({ items: [], add: i => set(s => ({ items: [...s.items, i] })) }), { name: 'cart-storage' } )); With Redux, use redux-persist or a store.subscribe that writes a throttled snapshot. Never store secrets like tokens in localStorage.
24

Show a basic React Router setup with a home page, a product page and a 404 page.

Save to ▾
Show answer
In React Router v7 (library mode) you can use declarative routes. import { BrowserRouter, Routes, Route } from 'react-router'; function App() { return ( <BrowserRouter> <Routes> <Route path="/" element={<Home />} /> <Route path="/products/:id" element={<Product />} /> <Route path="*" element={<NotFound />} /> </Routes> </BrowserRouter> ); } The * path catches any unmatched URL.
25

How do you read a dynamic URL parameter like a product id?

Save to ▾
Show answer
Define the segment with a colon and read it with useParams. // route: <Route path="/products/:id" element={<Product />} /> import { useParams } from 'react-router'; function Product() { const { id } = useParams(); const { data } = useQuery({ queryKey: ['product', id], queryFn: () => getProduct(id) }); return <h1>{data?.name}</h1>; } Params are always strings, so convert with Number(id) if needed.
26

How do you create a protected route that redirects users who are not logged in?

Save to ▾
Show answer
Create a wrapper that checks auth and either renders the child routes or redirects. import { Navigate, Outlet, useLocation } from 'react-router'; function RequireAuth() { const { user } = useAuth(); const location = useLocation(); if (!user) return <Navigate to="/login" replace state={{ from: location }} />; return <Outlet />; } <Route element={<RequireAuth />}> <Route path="/account" element={<Account />} /> </Route> The server must still enforce authorisation.
27

How do you navigate programmatically after a form submits?

Save to ▾
Show answer
Use the useNavigate hook. import { useNavigate } from 'react-router'; function CheckoutForm() { const navigate = useNavigate(); async function handleSubmit(e) { e.preventDefault(); const order = await placeOrder(); navigate(`/orders/${order.id}`, { replace: true }); } return <form onSubmit={handleSubmit}>...</form>; } replace: true stops the user from going back to the submitted form; navigate(-1) goes back.
28

How do you read and update query parameters like ?page=2&sort=price?

Save to ▾
Show answer
Use useSearchParams, which works like useState for the query string. import { useSearchParams } from 'react-router'; function ProductList() { const [params, setParams] = useSearchParams(); const page = Number(params.get('page') ?? 1); const sort = params.get('sort') ?? 'popular'; const next = () => setParams({ page: String(page + 1), sort }); return <Pagination page={page} onNext={next} />; } Keeping filters in the URL makes them shareable and bookmarkable.
29

How do you handle a form with multiple controlled inputs using one change handler?

Save to ▾
Show answer
Use the input's name attribute to update the right field. function SignupForm() { const [form, setForm] = useState({ name: '', email: '', city: '' }); const handleChange = e => { const { name, value } = e.target; setForm(f => ({ ...f, [name]: value })); }; const handleSubmit = e => { e.preventDefault(); console.log(form); }; return ( <form onSubmit={handleSubmit}> <input name="name" value={form.name} onChange={handleChange} /> <input name="email" value={form.email} onChange={handleChange} /> <button>Submit</button> </form> ); }
30

Show a login form using React Hook Form with Zod validation.

Save to ▾
Show answer
Define a schema and pass it through zodResolver. const schema = z.object({ email: z.string().email('Invalid email'), password: z.string().min(8, 'Min 8 characters') }); function Login() { const { register, handleSubmit, formState: { errors, isSubmitting } } = useForm({ resolver: zodResolver(schema) }); return ( <form onSubmit={handleSubmit(data => login(data))}> <input {...register('email')} /> {errors.email && <p>{errors.email.message}</p>} <input type="password" {...register('password')} /> <button disabled={isSubmitting}>Login</button> </form> ); }
31

How do you upload a file with a preview in React?

Save to ▾
Show answer
Use a file input, create a preview URL and send FormData. function AvatarUpload() { const [file, setFile] = useState(null); const preview = useMemo(() => file && URL.createObjectURL(file), [file]); useEffect(() => () => preview && URL.revokeObjectURL(preview), [preview]); async function upload() { const fd = new FormData(); fd.append('avatar', file); await fetch('/api/avatar', { method: 'POST', body: fd }); } return (<> <input type="file" accept="image/*" onChange={e => setFile(e.target.files[0])} /> {preview && <img src={preview} alt="preview" />} <button onClick={upload}>Upload</button> </>); }
32

How do you implement code splitting with React.lazy and Suspense?

Save to ▾
Show answer
Load a component only when it is needed with a dynamic import. import { lazy, Suspense } from 'react'; const Reports = lazy(() => import('./Reports')); function App() { return ( <Suspense fallback={<Spinner />}> {showReports && <Reports />} </Suspense> ); } The bundler creates a separate chunk for Reports, reducing the initial JavaScript download.
33

How do you find what is making a React page slow?

Save to ▾
Show answer
Use the React DevTools Profiler: record an interaction, then check the flame chart for components with long render times and the reason each one re-rendered (enable 'record why each component rendered'). Pair it with the Chrome Performance panel for long tasks, layout and network. Enable highlight updates to spot unnecessary renders. Measure first, fix the biggest cost, then measure again in a production build.
34

How do you analyse and reduce JavaScript bundle size?

Save to ▾
Show answer
Generate a visual report with rollup-plugin-visualizer in Vite or @next/bundle-analyzer in Next.js. Look for large dependencies, duplicate packages and code that loads on every page. Fixes include importing only what you need, replacing heavy libraries such as moment with date-fns or dayjs, lazy loading routes and rarely used components, and checking that libraries support tree shaking. Track size in CI to prevent regressions.
35

How do you optimise images in a React app?

Save to ▾
Show answer
Serve modern formats like WebP or AVIF, resize images to their display size and use srcset with sizes for responsive images. Add loading="lazy" for images below the fold and fetchpriority="high" for the main hero image. Always set width and height or aspect-ratio to prevent layout shift. In Next.js, the next/image component automates resizing, formats and lazy loading.
36

Write a test for a counter component that increments on click.

Save to ▾
Show answer
Use React Testing Library with user-event. import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import Counter from './Counter'; test('increments count', async () => { const user = userEvent.setup(); render(<Counter />); await user.click(screen.getByRole('button', { name: '+' })); expect(screen.getByRole('heading')).toHaveTextContent('1'); }); toHaveTextContent comes from @testing-library/jest-dom.
37

How do you test a component that fetches data from an API?

Save to ▾
Show answer
Mock the network, not fetch itself, using Mock Service Worker (MSW), then wait for the UI. const server = setupServer( http.get('/api/users', () => HttpResponse.json([{ id: 1, name: 'Asha' }])) ); beforeAll(() => server.listen()); afterEach(() => server.resetHandlers()); afterAll(() => server.close()); test('shows users', async () => { render(<Users />); expect(await screen.findByText('Asha')).toBeInTheDocument(); });
38

How do you mock a module in Vitest?

Save to ▾
Show answer
Use vi.mock with a factory, then control return values per test. import { vi } from 'vitest'; import { getProfile } from './api'; vi.mock('./api', () => ({ getProfile: vi.fn() })); test('shows name', async () => { getProfile.mockResolvedValue({ name: 'Ravi' }); render(<Profile />); expect(await screen.findByText('Ravi')).toBeInTheDocument(); }); In Jest, the same pattern uses jest.mock and jest.fn.
39

How do you test a custom hook?

Save to ▾
Show answer
Use renderHook from React Testing Library and wrap state changes in act. import { renderHook, act } from '@testing-library/react'; import { useCounter } from './useCounter'; test('increments', () => { const { result } = renderHook(() => useCounter(5)); act(() => result.current.increment()); expect(result.current.count).toBe(6); }); If the hook needs context, pass a wrapper option with the provider.
40

How do you test a component that depends on Redux and React Router?

Save to ▾
Show answer
Create a custom render helper that wraps the UI with real providers. function renderWithProviders(ui, { route = '/', preloadedState } = {}) { const store = configureStore({ reducer: rootReducer, preloadedState }); return render( <Provider store={store}> <MemoryRouter initialEntries={[route]}>{ui}</MemoryRouter> </Provider> ); } Using a real store with preloaded state is better than mocking useSelector, because it tests actual behaviour.
41

How do you use React DevTools for debugging?

Save to ▾
Show answer
The Components tab shows the component tree; selecting one shows its props, state, hooks and context values, which you can edit live to test behaviour. You can find which component rendered a DOM node, view the owner chain and jump to source. The Profiler tab records renders and shows why each component re-rendered. Combined with browser breakpoints and console logs, it speeds up most UI debugging.
42

How do you include accessibility checks in your React tests?

Save to ▾
Show answer
Use jest-axe or vitest-axe to run axe-core on rendered components. import { axe } from 'vitest-axe'; test('form has no a11y violations', async () => { const { container } = render(<SignupForm />); expect(await axe(container)).toHaveNoViolations(); }); Also query by role and label in tests, run Lighthouse or axe DevTools in the browser, and test keyboard navigation manually, since automated tools catch only part of the issues.
43

Show a Server Function used to handle a form in Next.js.

Save to ▾
Show answer
A function marked 'use server' runs on the server and can be passed to a form action. // app/actions.ts 'use server'; import { revalidatePath } from 'next/cache'; export async function addTodo(formData: FormData) { const title = String(formData.get('title')); await db.todo.create({ data: { title } }); revalidatePath('/todos'); } // app/todos/page.tsx <form action={addTodo}> <input name="title" required /> <button>Add</button> </form> Always validate input and check authorisation inside the function.
44

How do you fetch data in a Next.js App Router page?

Save to ▾
Show answer
Make the page an async Server Component and await data directly. // app/products/[id]/page.tsx export default async function ProductPage({ params }) { const { id } = await params; const product = await getProduct(id); if (!product) notFound(); return ( <main> <h1>{product.name}</h1> <AddToCartButton id={id} /> </main> ); } AddToCartButton is a Client Component. In recent Next.js versions params is a promise.
45

How do you set SEO metadata in a Next.js App Router page?

Save to ▾
Show answer
Export a metadata object or a generateMetadata function from a page or layout. export async function generateMetadata({ params }) { const { slug } = await params; const post = await getPost(slug); return { title: post.title, description: post.summary, alternates: { canonical: `/blog/${slug}` }, openGraph: { title: post.title, images: [post.cover] } }; } Next.js renders the tags in the head during server rendering.
46

How do you write a generic, type-safe List component in TypeScript?

Save to ▾
Show answer
Use a type parameter so the item type flows to the render callback. type ListProps<T> = { items: T[]; getKey: (item: T) => string | number; renderItem: (item: T) => React.ReactNode; }; function List<T>({ items, getKey, renderItem }: ListProps<T>) { return <ul>{items.map(i => <li key={getKey(i)}>{renderItem(i)}</li>)}</ul>; } <List items={users} getKey={u => u.id} renderItem={u => u.name} />

Frequently asked questions

Why are practical questions important?

Most technical rounds include at least one live task. Practising the exact typing and reasoning makes you calm and fast in the real round.

How should I answer them?

Talk while you work: say your approach first, write the simplest correct version, then improve it and mention edge cases.