56React for SDET
What you will master here
- React mental model: components, props, state
- Hooks: useState, useEffect, useRef, useMemo, useCallback, useContext
- Reconciliation + keys
- Component testing with React Testing Library
- Mocking API calls (MSW)
- Playwright Component Testing
- Common React bugs SDETs catch
56.1 Mental model
A React app is a tree of components. Each component is a function that returns JSX (HTML-like markup). State changes trigger re-renders; React diffs the new tree against the previous one and updates only the changed DOM nodes (reconciliation).
// Functional component
function Greeting({ name }: { name: string }) {
return <h1>Hello, {name}</h1>;
}
// State + handler
function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(c => c + 1)}>
Clicked {count} times
</button>
);
}
56.2 Hooks reference
| Hook | Purpose |
|---|---|
| useState | Local state |
| useEffect | Side effects (fetch, subscriptions); cleanup on unmount |
| useRef | Mutable value that persists across renders without triggering re-render |
| useMemo | Cache expensive computation by dependency keys |
| useCallback | Cache function identity (prevent child re-renders) |
| useContext | Read context value |
| useReducer | State with reducer pattern (complex state) |
| useLayoutEffect | Synchronous effect (before paint) |
56.3 Reconciliation + keys
// BAD — without keys, React can't tell which item is which
{items.map((item, i) => <Row data={item} />)}
// GOOD — stable key
{items.map(item => <Row key={item.id} data={item} />)}
Missing keys cause subtle bugs: input state attaches to the wrong row after reordering. SDETs catch these in drag-and-drop / sortable lists.
56.4 Testing with React Testing Library
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { LoginForm } from './LoginForm';
it('submits credentials', async () => {
const onSubmit = vi.fn();
render(<LoginForm onSubmit={onSubmit} />);
await userEvent.type(screen.getByLabelText('Email'), 'alice@test.com');
await userEvent.type(screen.getByLabelText('Password'), 'p4ssw0rd');
await userEvent.click(screen.getByRole('button', { name: 'Sign in' }));
expect(onSubmit).toHaveBeenCalledWith({
email: 'alice@test.com',
password: 'p4ssw0rd',
});
});
Same locator philosophy as PlaywrightRTL pushes getByRole / getByLabel — same accessible-name approach. Skills transfer between unit and E2E.
56.5 Mocking API calls with MSW
// src/mocks/handlers.ts
import { http, HttpResponse } from 'msw';
export const handlers = [
http.get('/api/users/:id', ({ params }) =>
HttpResponse.json({ id: params.id, name: 'Alice' })),
];
// In a test
import { server } from './mocks/server';
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
it('shows user name from API', async () => {
render(<UserPage id="42" />);
expect(await screen.findByText('Alice')).toBeInTheDocument();
});
56.6 Playwright Component Testing
npm init playwright@latest -- --ct # pick framework: React
// Button.spec.tsx
import { test, expect } from '@playwright/experimental-ct-react';
import { Button } from './Button';
test('renders + click', async ({ mount }) => {
let clicked = false;
const cmp = await mount(<Button onClick={() => (clicked = true)}>Buy</Button>);
await cmp.click();
expect(clicked).toBe(true);
});
Component tests render in a real browser via Playwright but mount just the component (no app shell). Faster than full E2E, more realistic than jsdom.
56.7 Common React bugs SDETs catch
- Stale closures — onClick captures old state from render. Symptom: counter increments by 1 even after 3 clicks. Fix: functional updater
setCount(c => c+1). - Missing keys — list reorder bug; old input state appears on wrong row.
- useEffect over-fires — missing dep triggers infinite loop. ESLint react-hooks/exhaustive-deps catches.
- Race condition in fetch — old request resolves AFTER new one; old data overwrites new. Fix: AbortController or "ignore" flag.
- Memory leak from event listeners / timers — useEffect cleanup not returning a function.
- Hydration mismatch (SSR) — server HTML differs from client first render → warning + flicker.
- Accessibility bugs — div onClick (no role/keyboard), missing labels.
56.8 React 18 / 19 highlights
- Concurrent rendering — React can pause/resume work; users see smoother UX
- useTransition — marks updates as non-urgent
- Suspense for data fetching — declarative loading states
- Server Components (Next.js) — render on the server, ship less JS
- Actions + useActionState (React 19) — built-in form mutation patterns
Module 59 — React Q&A
What is JSX?
Syntactic sugar over
React.createElement(...). <h1>Hi</h1> compiles to React.createElement('h1', null, 'Hi'). The compiler (Babel/SWC) handles it.useState vs useRef?
useState: when the value affects rendering. Updates trigger re-render. useRef: when the value needs to persist across renders but should NOT cause re-render (DOM ref, mutable counter, previous value).
Why are list keys important?
React uses keys to match old and new list items during diffing. Without stable keys, React can mis-associate items — state attaches to wrong row, animations flicker, performance degrades. Use a stable id, not the array index.
What's a stale closure bug?
A function captured the value of state from the render where it was created. By the time it runs, state has changed. Symptom:
setCount(count + 1) in a setTimeout uses the old count. Fix: functional updater setCount(c => c + 1).useEffect cleanup function?
The return value of useEffect — runs when the component unmounts or before the effect re-runs. Use to remove event listeners, cancel timers, abort fetches. Skipping cleanup = memory leaks.
How is React Testing Library different from Enzyme?
Enzyme inspected internal component state and let you call methods directly — fragile, coupled to implementation. RTL focuses on observable behaviour through the DOM (text, roles, accessibility) — refactor-friendly, mirrors how real users / Playwright would interact.
When would you use Playwright Component Testing over RTL?
When the component depends on real browser behaviour (layout, real fonts, real CSS animations, drag-drop). RTL uses jsdom — fast but missing DOM features. Playwright CT uses real Chromium — slower but accurate.
What's hydration mismatch?
In SSR, server sends HTML. Client renders the same components and "hydrates" — attaches event listeners. If client output differs from server (e.g. timezone-dependent date), React warns and may re-render. Common when using
new Date() in render.Concurrent rendering — what does it change for SDET?
React 18+ can interrupt/resume rendering. Updates marked with useTransition are non-urgent. Tests should be resilient: don't depend on render order; use web-first assertions that retry until UI settles.
Most common a11y issue in React?
Divs as buttons. Visually look like buttons (cursor pointer, click handler) but have no role, no keyboard support, no label. Fix: use
<button>, or div with role="button" + tabIndex + onKeyDown.