React for SDET

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

HookPurpose
useStateLocal state
useEffectSide effects (fetch, subscriptions); cleanup on unmount
useRefMutable value that persists across renders without triggering re-render
useMemoCache expensive computation by dependency keys
useCallbackCache function identity (prevent child re-renders)
useContextRead context value
useReducerState with reducer pattern (complex state)
useLayoutEffectSynchronous 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

56.8 React 18 / 19 highlights

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.