DS DevShelfHub Projects · AI tools
Cheatsheets / React
Cheatsheet · Dev tooling

React: Hooks, State, Context and JSX Reference Guide

By DevShelfHub

Components, JSX, hooks, context, suspense, refs, forms — the day-to-day React API as you actually use it.

87 items ◷ 7 min Hooks State JSX

Start hereQuick start · 6 you’ll reach for daily

Componentfunction Foo() {…}
Stateconst [x, setX] = useState(0)
EffectuseEffect(() => …, [dep])
MemouseMemo(() => …, [dep])
RefuseRef<HTMLInputElement>(null)
ContextuseContext(MyContext)

Target versions · paceVersions

Targets: react ≥ 19 react-dom ≥ 19 typescript ≥ 5.5

Sheet pins to React 19+. use(), the new <form action> + useActionState, server / async components, and the compiler are stable surface. Class components and legacy lifecycle methods (componentDidMount, …) still work but the rows below show function-component patterns.

scaffold · deps · toolingSetup

bash
# New app — Vite is the default starter
npm create vite@latest my-app -- --template react-ts
cd my-app && npm install && npm run dev

# Or use a meta-framework
npm create next-app@latest my-app          # Next.js
npm create remix@latest my-app             # Remix / React Router

# Add packages you'll reach for daily
npm install @tanstack/react-query          # server state
npm install zustand                        # local global state
npm install react-hook-form zod            # forms + validation
npm install -D vitest @testing-library/react @testing-library/user-event

# Type-check + lint (TS projects)
npx tsc --noEmit
npx eslint . --fix

where things liveCommon imports

import { useState, useEffect, useRef, useMemo, useCallback, useReducer, useContext } from "react"Core hooks.
import { useId, useDeferredValue, useTransition, useSyncExternalStore } from "react"Less-common but stdlib.
import { use, useOptimistic, useActionState } from "react"React 19 additions.
import { Suspense, lazy, Fragment, memo, forwardRef, createContext } from "react"Component plumbing.
import type { ReactNode, FC, ComponentProps, FormEvent, ChangeEvent } from "react"TS types.
import { createRoot } from "react-dom/client"App entry. Replaces legacy ReactDOM.render.
import { createPortal, flushSync } from "react-dom"Portals + sync render escape hatch.
import { ErrorBoundary } from "react-error-boundary"Standard 3rd-party error boundary.
import { QueryClient, QueryClientProvider, useQuery } from "@tanstack/react-query"Server-state caching.

build the treeComponents

function Foo(props: Props) { return <div>…</div>; }Preferred Function components.
class Foo extends Component<P, S> { render() {} }Legacy Class components.
memo(Component)Skip re-render when props are Object.is-equal. Use after profiling.
forwardRef((props, ref) => …)Pass a ref through. React 19 also accepts ref as a normal prop.
lazy(() => import("./X"))Code-split. Pair with <Suspense>.
<Fragment>…</Fragment> or <>…</>Group nodes without a wrapper.
props.children: ReactNodeSlot pattern. Render-prop / asChild variants are common.
createPortal(node, document.body)Render outside the parent DOM tree.
javascript
import { useState, type ReactNode } from "react";

type ButtonProps = {
  onClick:  () => void;
  variant?: "primary" | "ghost";
  disabled?: boolean;
  children: ReactNode;
};

export function Button({
  onClick, variant = "primary", disabled = false, children,
}: ButtonProps) {
  return (
    
  );
}

// Composition + state
export function Counter() {
  const [count, setCount] = useState(0);
  return (
    

Count: {count}

); }

describing UIJSX

{value}Embed an expression. Single braces.
className="btn primary"class renamed to className.
htmlFor="id"for renamed.
style={ { color: "red", fontSize: 14 } }Inline styles take a JS object. camelCase keys.
{cond && <X />}Conditional render via short-circuit.
{cond ? <A /> : <B />}Ternary form. Prefer over nested &&.
{items.map(i => <Row key={i.id} item={i} />)}Lists. Always pass key.
{...spreadProps}Spread an object as attributes. Be careful with unknown keys.
<input defaultValue={x} />Uncontrolled. Initial value only.
<input value={x} onChange={…} />Controlled. React owns the value.

parent → child dataProps

type Props = { title: string; onClick: () => void }Typed props with TS.
function Foo({ title, onClick }: Props)Destructure in the param list.
defaults via destructuring{ variant = "primary" } = props.
type Props = ComponentProps<"button"> & { variant?: … }Extend HTML props instead of re-declaring.
children: ReactNodeMost common slot type.
render-prop / function-as-child{(state) => <X />} — works but use composition first.
Never mutate propsRead-only. Reassigning silently or via spread doesn’t propagate.

state & effectsHooks

useState(initial)Local state. Pass a function for lazy init.
setX(prev => next)Functional updater. Use when next depends on prev.
useEffect(fn, deps)Side effects after commit. Return cleanup.
useLayoutEffect(fn, deps)Synchronous after layout, before paint. Layout reads.
useRef<T>(initial)Mutable container; doesn’t trigger re-render.
useMemo(() => expensive(), deps)Memoise a value.
useCallback(fn, deps)Memoise a function. Stable for child-component prop equality.
useReducer(reducer, initial)Several related state fields. Cleaner than three useStates.
useContext(MyContext)Consume context.
useId()Stable unique id for accessibility (label htmlFor).
useTransition() / useDeferredValue(x)Mark updates as low-priority. Smoother UIs.
useSyncExternalStoreSubscribe to external stores safely (concurrent-safe).
use(promise) / use(context)19 Suspends until a promise resolves; read context in nested code.
useActionState(action, init)19 Pair with <form action>.
useOptimistic(state, reducer)19 Optimistic UI updates that revert on error.
javascript
import { useState, useEffect, useRef, useMemo, useCallback, useReducer } from "react";

// State
const [count, setCount] = useState(0);
setCount(c => c + 1);                         // functional updater — safer in async paths

// Effects — run after commit; cleanup on unmount or dep change
useEffect(() => {
  const ctrl = new AbortController();
  fetch("/api/x", { signal: ctrl.signal }).then(/* ... */);
  return () => ctrl.abort();                  // cleanup
}, [/* deps */]);

// Refs — mutable, no re-render
const inputRef = useRef(null);
inputRef.current?.focus();

// Memoisation — only if profiler shows the cost
const sorted   = useMemo(() => items.toSorted(byName), [items]);
const onSubmit = useCallback((v: string) => save(v), [save]);

// Reducer — when several state fields change together
type State = { items: string[]; loading: boolean };
type Action =
  | { type: "load" }
  | { type: "loaded"; items: string[] };

function reducer(s: State, a: Action): State {
  switch (a.type) {
    case "load":   return { ...s, loading: true };
    case "loaded": return { items: a.items, loading: false };
  }
}
const [state, dispatch] = useReducer(reducer, { items: [], loading: false });

prop drilling escape hatchContext

const ThemeCtx = createContext<Theme | null>(null)Default value. Type with TS.
<ThemeCtx value={theme}> / <ThemeCtx.Provider value={…}>React 19 lets you use Ctx directly.
const theme = useContext(ThemeCtx)Consume. Re-renders subscribers on value change.
Split contextsDon’t put everything in one. Frequent + rare updates → separate providers.
Memoise the value objectuseMemo(() => ({ a, b }), [a, b]). Otherwise every render reseats everyone.
Custom hook wrappersuseTheme() hides context + asserts non-null.
Context is not a state manager. For deeply-shared mutable state with selectors, reach for Zustand, Jotai, or Redux Toolkit. Context updates everyone subscribed; selectors don’t.

async UISuspense & data fetching

<Suspense fallback={…}>Boundary that catches suspending children.
<ErrorBoundary fallback={…}>Pair with Suspense for errors.
const data = use(promise)19 Suspend on a promise. Pass a cached one!
useSuspenseQuery({ queryKey, queryFn })react-query Suspense-compatible variant.
streaming SSR / Server ComponentsSuspense streams chunks as they resolve.
startTransition(() => setX(…))Mark a state update as non-urgent — doesn’t block input.

controlled inputsForms

<input value={v} onChange={e => setV(e.target.value)} />Controlled input.
<input defaultValue={v} ref={ref} />Uncontrolled. Read via ref.
e.preventDefault() in onSubmitBlock native form submit before client-side logic.
<form action={serverAction}>19 Server actions / progressive enhancement.
useActionState(action, initial)19 Reducer-shaped state with pending tracking.
react-hook-form + zodBattle-tested combo: uncontrolled perf + schema validation.
FormData / form.elementsNative APIs. Useful in server actions.
javascript
// react-hook-form + zod — the modern default
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";

const Schema = z.object({
  email: z.string().email(),
  age:   z.number().int().min(13),
});
type FormValues = z.infer;

export function SignupForm() {
  const {
    register, handleSubmit, formState: { errors, isSubmitting },
  } = useForm({ resolver: zodResolver(Schema) });

  const onSubmit = handleSubmit(async (data) => {
    await fetch("/api/signup", { method: "POST", body: JSON.stringify(data) });
  });

  return (
    
{errors.email &&

{errors.email.message}

} {errors.age &&

{errors.age.message}

}
); }

render dynamic childrenLists & keys

{items.map(i => <Row key={i.id} item={i} />)}Stable keys preserve component state.
Never use array index as key for reorderable listsSame index, different item => state leaks.
{items.toSorted(byName).map(…)}Don’t mutate the source array. Use non-mutating methods.
Wrap in <Fragment key={…}>When you need a key on a fragment.
Virtualise long lists@tanstack/react-virtual / react-window.

measure firstPerformance

React DevTools ProfilerFind what re-renders + why before tweaking.
memo / useMemo / useCallbackOnly after profiling. Premature use slows code.
Stable references for propsInline { a: 1 } objects break memo.
Lift state down, not upKeep state in the smallest subtree that needs it.
Code-split with lazy + SuspenseTrim initial bundle.
useTransition / useDeferredValueDon’t block input on heavy renders.
key as state resetChange the key to throw away component state — faster than imperative resets.
React CompilerAuto-memoises components and hooks. Opt in via the Babel plugin.

suspense + react-query + error boundaryEnd-to-end · Async data UI

Data fetching with useSuspenseQuery, error boundary, and a Suspense fallback. The shape you reach for once you need real loading / error states.

javascript
// End-to-end: data fetching with Suspense + error boundary + react-query.
import { Suspense } from "react";
import { ErrorBoundary } from "react-error-boundary";
import {
  QueryClient, QueryClientProvider, useSuspenseQuery,
} from "@tanstack/react-query";

const qc = new QueryClient({ defaultOptions: { queries: { staleTime: 30_000 } } });

type User = { id: number; name: string };

function UserCard({ id }: { id: number }) {
  const { data } = useSuspenseQuery({
    queryKey: ["user", id],
    queryFn:  async ({ signal }) => {
      const r = await fetch(`/api/users/${id}`, { signal });
      if (!r.ok) throw new Error(`HTTP ${r.status}`);
      return r.json();
    },
  });
  return 

Hi, {data.name}

; } export function App() { return ( Something broke.

}> Loading…

}>
); }

Best practiceGood to know

Co-locate state with the smallest consumer. Lift state up only when two siblings need it. State at the top of a tree means every keystroke re-renders the whole subtree.
Reach for react-query (or SWR) for server state. useEffect + useState for fetching is a well-trodden tar pit (race conditions, no cache, no retry). The libraries solve it once.
Type your hooks with TypeScript. useRef<HTMLInputElement>(null), useState<User | null>(null) — catches null-deref + wrong-DOM-element bugs at compile time.

Common trapsWatch out for

Closures capture stale values inside effects. Missing a dependency in useEffect means the effect reads an old setX / state. Use the functional updater or list the dep.
Array index as key reuses the wrong instance. Reordering or filtering keeps the old component mounted at that index with the new data. Use a stable ID.
Avoid setState in render. It triggers an infinite render loop. Move it into an effect, an event handler, or compute the value derived from state instead of storing it.

Go deeperSee also

React FAQ

What is React used for?

React is a JavaScript library for building user interfaces through reusable components. It is used to build single-page apps, dashboards, e-commerce frontends, and mobile apps via React Native. React 19 added server components, the compiler, and use() for Suspense-based data fetching.

What are React hooks?

Hooks are functions that let function components use state, side effects, context, refs, and performance optimizations. The built-in hooks are useState, useEffect, useContext, useRef, useMemo, useCallback, useReducer, and useId. Custom hooks are regular functions that start with use.

What is the difference between useState and useReducer?

useState is for simple, independent state values — a number, string, or boolean. useReducer is better when the next state depends on the previous one, when state has multiple sub-values that move together, or when the update logic is complex enough to warrant a named reducer function.

What is React Context used for?

Context provides a way to pass data through the component tree without prop drilling. Create a context with createContext, wrap the subtree in a Provider, and read the value anywhere inside with useContext. Use it for themes, authenticated user data, locale settings, and similar global state.

Is React free to use?

Yes. React is open source and maintained by Meta under the MIT license. It is free for personal and commercial use with no restrictions.