
React Core Architecture
- 11 installs
- 6 repo stars
- Updated July 8, 2026
- openaec-foundation/react-claude-skill-package
Helps with frontend development tasks.
About
react-core-architecture is a Claude Code skill for frontend development. It helps solo builders move faster with AI-assisted development.
- react-core-architecture
- Frontend Development
- AI-coding skill
React Core Architecture by the numbers
- 11 all-time installs (skills.sh)
- Ranked #1,658 of 2,245 Frontend Development skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/openaec-foundation/react-claude-skill-package --skill react-core-architectureAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 11 |
|---|---|
| repo stars | ★ 6 |
| Last updated | July 8, 2026 |
| Repository | openaec-foundation/react-claude-skill-package ↗ |
What it does
Helps with frontend development tasks.
Files
react-core-architecture
Quick Reference
Architecture Layers
| Layer | Role | Key Concept |
|---|---|---|
| React Elements | Lightweight descriptions of UI | Immutable objects created by JSX/createElement |
| Components | Functions that return elements | Pure functions of props and state |
| Fiber Tree | Internal work-in-progress tree | Enables incremental rendering and prioritization |
| Reconciler | Diffing algorithm | Compares previous and next element trees |
| Renderer | Platform-specific output | react-dom for web, react-native for mobile |
Core Principles
| Principle | Rule |
|---|---|
| Unidirectional Data Flow | Data ALWAYS flows from parent to child via props |
| Declarative UI | ALWAYS describe what the UI should look like, NEVER imperatively mutate the DOM |
| Composition over Inheritance | ALWAYS compose components, NEVER use class inheritance for component reuse |
| Pure Rendering | The render phase MUST be a pure function of props and state |
| Immutable Updates | NEVER mutate state or props directly; ALWAYS create new references |
React Element vs Component
| Concept | What It Is | Example |
|---|---|---|
| React Element | Immutable plain object describing a DOM node or component | { type: 'div', props: { children: 'Hello' } } |
| Component | Function that accepts props and returns React elements | function Greeting({ name }: Props) { return <h1>{name}</h1>; } |
| Fiber | Internal mutable work unit tracking a component instance | Not directly accessible; managed by React internals |
---
Critical Warnings
NEVER mutate state or props during rendering -- rendering MUST be a pure calculation. Mutations cause inconsistent UI and break concurrent features.
NEVER rely on render timing or count -- React MAY call your component multiple times, skip renders, or pause and resume rendering. StrictMode double-invokes components in development.
NEVER perform side effects in the render phase (network requests, subscriptions, DOM mutations) -- ALWAYS use useEffect or event handlers for side effects.
NEVER use inheritance to share behavior between components -- ALWAYS use composition (children, render props, or custom hooks).
NEVER call root.render() where hydrateRoot() is needed -- for server-rendered HTML, ALWAYS use hydrateRoot to preserve server markup and attach event handlers.
NEVER assume synchronous DOM updates after root.render() -- rendering is asynchronous. Use flushSync() ONLY when synchronous behavior is explicitly required.
---
Rendering Model
JSX Compilation
JSX is syntactic sugar for React.createElement() calls:
// JSX (what you write)
<Greeting name="Taylor" />
// Compiled output (what React sees)
createElement(Greeting, { name: 'Taylor' })The returned React element is an immutable object:
{
type: Greeting, // Component function or string tag
props: { name: 'Taylor' },
key: null,
ref: null
}ALWAYS use capital letters for component names in JSX -- lowercase names resolve to HTML tags, not components.
Three-Phase Rendering Cycle
React updates the screen in three sequential steps:
| Phase | What Happens | Interruptible? |
|---|---|---|
| 1. Trigger | Initial root.render() call or a state update via setState | N/A |
| 2. Render | React calls component functions and diffs the element tree | Yes (concurrent mode) |
| 3. Commit | React applies minimal DOM mutations to match the new tree | No (synchronous) |
After the commit phase, the browser paints the updated screen.
Render Phase (Pure)
- React calls your component function to produce a new element tree
- Compares the new tree with the previous tree (reconciliation)
- In concurrent mode (React 18+), this phase is interruptible -- React can pause, resume, or discard work
- MUST be pure: no side effects, no DOM mutations, no subscriptions
Commit Phase (Synchronous)
- React applies the minimal set of DOM changes identified during reconciliation
- Runs
useLayoutEffectcleanup and setup synchronously - The browser paints the screen
- Runs
useEffectcleanup and setup asynchronously after paint
Reconciliation Algorithm
React's diffing strategy uses two key heuristics:
1. Different element types produce different trees -- React tears down the old subtree and builds a new one 2. Keys identify which children remain stable across re-renders -- ALWAYS provide stable keys for list items
// React preserves <input> because the element type and position match
<div>
<input value={text} /> {/* Same position, same type = preserved */}
</div>---
Fiber Architecture (React 16+)
The Fiber reconciler replaced the legacy stack reconciler to enable:
| Capability | Description |
|---|---|
| Incremental rendering | Split rendering work into chunks across multiple frames |
| Priority scheduling | Urgent updates (user input) preempt lower-priority work |
| Pause and resume | Interrupt in-progress work without losing progress |
| Concurrent rendering | Prepare multiple UI versions simultaneously (React 18+) |
Each fiber node represents a component instance and contains:
type-- the component function or host element tagstateNode-- the DOM node (for host elements) or component instancechild,sibling,return-- tree navigation pointersmemoizedState-- the linked list of hooks for this componentpendingProps,memoizedProps-- current and previous propslanes-- priority bits for scheduling (React 18+)
NEVER access fiber internals directly -- they are private implementation details that change between React versions.
---
Component Lifecycle
Function Component Lifecycle
Mount: Component called -> Elements created -> DOM inserted -> Effects run
Update: State/props change -> Component re-called -> Reconciliation -> DOM patched -> Effects re-run
Unmount: Effect cleanups run -> DOM removed| Phase | What Runs | When |
|---|---|---|
| Mount | Component function, then useEffect callbacks | First render, after DOM insertion |
| Update | Component function, then useEffect cleanups + callbacks (if deps changed) | On state or props change |
| Unmount | useEffect cleanup functions | When component is removed from tree |
Entry Point: createRoot
import { createRoot } from 'react-dom/client';
import { StrictMode } from 'react';
import App from './App';
const root = createRoot(document.getElementById('root')!, {
onCaughtError: (error, errorInfo) => {
console.error('Caught:', error, errorInfo.componentStack);
},
onUncaughtError: (error, errorInfo) => {
console.error('Uncaught:', error, errorInfo.componentStack);
},
});
root.render(
<StrictMode>
<App />
</StrictMode>
);ALWAYS wrap the root in <StrictMode> during development to detect impure renders, missing effect cleanups, and deprecated APIs.
---
Component Tree Model
Render Tree
The render tree represents the component hierarchy for a single render pass:
- Nodes are React components (not HTML elements)
- Root node is the top-level component passed to
root.render() - Top-level components near the root affect performance of all descendants
- Leaf components at the bottom are frequently re-rendered
The tree changes dynamically with conditional rendering -- different state produces different subtrees.
Unidirectional Data Flow
State (parent) --> Props (child) --> Props (grandchild)
^ |
| |
+-------- Callbacks (events) <---------+Data flows DOWN through props. Communication UP happens through callback functions passed as props. NEVER pass data upward by mutating parent state from a child without using a callback.
---
StrictMode Behavior (Development Only)
| Check | Method | Purpose |
|---|---|---|
| Impure rendering | Double-invokes component functions | Catches render-phase mutations |
| Missing effect cleanup | Runs setup -> cleanup -> setup cycle | Catches missing cleanup functions |
| Missing ref cleanup | Double ref callback cycle | Catches ref-related memory leaks |
| Deprecated APIs | Static warnings | Flags legacy lifecycle methods |
StrictMode checks run ONLY in development. They have zero impact on production builds.
---
React 18 vs React 19
| Feature | React 18 | React 19 |
|---|---|---|
| Concurrent rendering | Introduced via createRoot | Stable, improved scheduling |
| Server Components | Experimental | Stable |
ref forwarding | Requires forwardRef() | ref is a regular prop |
| Context Provider | <MyContext.Provider value={}> | <MyContext value={}> |
| Form handling | Manual state management | Built-in Actions pattern |
use() API | Not available | Reads Promises and Context in render |
useActionState | Not available | Manages async action state |
useOptimistic | Not available | Optimistic UI updates |
| Metadata tags | Require react-helmet or similar | Native <title>, <meta>, <link> hoisting |
| Error callbacks | onRecoverableError only | onCaughtError, onUncaughtError, onRecoverableError |
---
Reference Links
- references/examples.md -- Working code examples for rendering, lifecycle, and tree structure
- references/api-table.md -- React core type reference and API signatures
- references/anti-patterns.md -- What NOT to do, with explanations
Official Sources
- https://react.dev/learn/render-and-commit
- https://react.dev/learn/understanding-your-ui-as-a-tree
- https://react.dev/reference/react/createElement
- https://react.dev/reference/react-dom/client/createRoot
- https://react.dev/reference/react/StrictMode
- https://react.dev/blog/2024/04/25/react-19
react-core-architecture -- Anti-Patterns
Render Phase Violations
NEVER: Mutate State During Render
// BAD: Mutating state during render causes infinite loops
function Counter({ items }: { items: string[] }): React.ReactElement {
const [count, setCount] = useState(0);
setCount(items.length); // NEVER -- triggers re-render during render
return <p>{count}</p>;
}
// GOOD: Derive values directly or use useEffect for side effects
function Counter({ items }: { items: string[] }): React.ReactElement {
const count = items.length; // Derive from props -- no state needed
return <p>{count}</p>;
}NEVER: Mutate Props or External Variables in Render
// BAD: Mutating an array during render is impure
let globalList: string[] = [];
function BadComponent({ item }: { item: string }): React.ReactElement {
globalList.push(item); // NEVER -- side effect in render phase
return <span>{item}</span>;
}
// GOOD: Use state or effects for mutations
function GoodComponent({ item }: { item: string }): React.ReactElement {
useEffect(() => {
globalList.push(item);
return () => {
globalList = globalList.filter(i => i !== item);
};
}, [item]);
return <span>{item}</span>;
}NEVER: Perform Side Effects in Render
// BAD: Fetch data during render
function UserProfile({ userId }: { userId: string }): React.ReactElement {
// NEVER -- network request in render phase
const response = fetch(`/api/users/${userId}`);
// ...
}
// GOOD: Use useEffect for side effects
function UserProfile({ userId }: { userId: string }): React.ReactElement {
const [user, setUser] = useState<User | null>(null);
useEffect(() => {
let cancelled = false;
fetch(`/api/users/${userId}`)
.then(res => res.json())
.then((data: User) => {
if (!cancelled) setUser(data);
});
return () => { cancelled = true; };
}, [userId]);
return user ? <h1>{user.name}</h1> : <p>Loading...</p>;
}---
Component Design Violations
NEVER: Use Inheritance for Component Reuse
// BAD: Class inheritance for shared behavior
class BaseComponent extends React.Component {
getFormattedDate() {
return new Date().toLocaleDateString();
}
}
class DateDisplay extends BaseComponent {
render() {
return <span>{this.getFormattedDate()}</span>;
}
}
// GOOD: Use composition with hooks or children
function usFormattedDate(): string {
return new Date().toLocaleDateString();
}
function DateDisplay(): React.ReactElement {
const date = usFormattedDate();
return <span>{date}</span>;
}NEVER: Create Components Inside Other Components
// BAD: Component defined inside render -- recreated every render
function Parent(): React.ReactElement {
// NEVER -- this creates a new component type on every render
// causing unmount/remount of the entire subtree and losing state
function Child(): React.ReactElement {
const [count, setCount] = useState(0); // State resets every parent render!
return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
}
return <Child />;
}
// GOOD: Define components at module scope
function Child(): React.ReactElement {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
}
function Parent(): React.ReactElement {
return <Child />;
}---
Reconciliation Anti-Patterns
NEVER: Use Array Index as Key for Dynamic Lists
interface Item {
id: string;
text: string;
}
// BAD: Index keys cause bugs when items are reordered, inserted, or deleted
function BadList({ items }: { items: Item[] }): React.ReactElement {
return (
<ul>
{items.map((item, index) => (
// NEVER for dynamic lists -- causes incorrect state association
<li key={index}>
<input defaultValue={item.text} />
</li>
))}
</ul>
);
}
// When an item is removed from the middle, inputs shift to wrong items
// GOOD: Use stable unique identifiers
function GoodList({ items }: { items: Item[] }): React.ReactElement {
return (
<ul>
{items.map(item => (
<li key={item.id}>
<input defaultValue={item.text} />
</li>
))}
</ul>
);
}NEVER: Generate Random Keys
// BAD: Random keys force remount on every render
function BadList({ items }: { items: string[] }): React.ReactElement {
return (
<ul>
{items.map(item => (
// NEVER -- Math.random() produces new key each render = full remount
<li key={Math.random()}>{item}</li>
))}
</ul>
);
}
// GOOD: Use stable identifiers from data
function GoodList({ items }: { items: Array<{ id: string; text: string }> }): React.ReactElement {
return (
<ul>
{items.map(item => (
<li key={item.id}>{item.text}</li>
))}
</ul>
);
}---
Entry Point Anti-Patterns
NEVER: Use createRoot for Server-Rendered HTML
// BAD: Discards all server-rendered markup, causes flash of empty content
import { createRoot } from 'react-dom/client';
const root = createRoot(document.getElementById('root')!);
root.render(<App />); // Server HTML is thrown away
// GOOD: Use hydrateRoot to attach to existing server markup
import { hydrateRoot } from 'react-dom/client';
hydrateRoot(document.getElementById('root')!, <App />);NEVER: Create Multiple Roots for the Same DOM Node
// BAD: Creating multiple roots for the same container
const container = document.getElementById('root')!;
const root1 = createRoot(container);
const root2 = createRoot(container); // NEVER -- undefined behavior
root1.render(<App />);
root2.render(<App />); // Overwrites root1
// GOOD: Reuse the same root instance
const root = createRoot(container);
root.render(<App />);
// Later, to update:
root.render(<UpdatedApp />); // Same root, new content---
State Management Anti-Patterns
NEVER: Pass Data Upward by Mutating Parent References
// BAD: Child mutates parent's data directly
function Parent(): React.ReactElement {
const data = { count: 0 };
return <Child data={data} />;
}
function Child({ data }: { data: { count: number } }): React.ReactElement {
return (
// NEVER -- mutating props breaks unidirectional data flow
<button onClick={() => { data.count += 1; }}>
{data.count}
</button>
);
}
// GOOD: Use callbacks to communicate upward
function Parent(): React.ReactElement {
const [count, setCount] = useState(0);
return <Child count={count} onIncrement={() => setCount(c => c + 1)} />;
}
function Child({ count, onIncrement }: {
count: number;
onIncrement: () => void;
}): React.ReactElement {
return <button onClick={onIncrement}>{count}</button>;
}NEVER: Mutate State Objects Directly
interface UserState {
name: string;
age: number;
}
// BAD: Mutating state object in place
function BadForm(): React.ReactElement {
const [user, setUser] = useState<UserState>({ name: '', age: 0 });
const handleNameChange = (e: React.ChangeEvent<HTMLInputElement>): void => {
user.name = e.target.value; // NEVER -- React does not detect this mutation
setUser(user); // Same reference, no re-render triggered
};
return <input value={user.name} onChange={handleNameChange} />;
}
// GOOD: Create new object with spread
function GoodForm(): React.ReactElement {
const [user, setUser] = useState<UserState>({ name: '', age: 0 });
const handleNameChange = (e: React.ChangeEvent<HTMLInputElement>): void => {
setUser(prev => ({ ...prev, name: e.target.value }));
};
return <input value={user.name} onChange={handleNameChange} />;
}---
StrictMode Anti-Patterns
NEVER: Rely on Render Count or Timing
// BAD: Assumes component renders exactly once on mount
let renderCount = 0;
function BadComponent(): React.ReactElement {
renderCount += 1; // In StrictMode: increments twice per mount
useEffect(() => {
// NEVER assume this runs once -- StrictMode runs setup->cleanup->setup
analytics.trackPageView();
}, []);
return <div>Renders: {renderCount}</div>;
}
// GOOD: Use refs for render counting, guard effects properly
function GoodComponent(): React.ReactElement {
const renderCountRef = useRef(0);
renderCountRef.current += 1;
useEffect(() => {
// Effect with proper cleanup works correctly even with double-invocation
const controller = new AbortController();
analytics.trackPageView({ signal: controller.signal });
return () => controller.abort();
}, []);
return <div>Renders: {renderCountRef.current}</div>;
}---
React 19 Migration Anti-Patterns
NEVER: Use forwardRef in React 19 (Deprecated)
// BAD in React 19: forwardRef is unnecessary
import { forwardRef } from 'react';
const MyInput = forwardRef<HTMLInputElement, { label: string }>(
({ label }, ref) => (
<label>
{label}
<input ref={ref} />
</label>
)
);
// GOOD in React 19: ref is a regular prop
function MyInput({ label, ref }: {
label: string;
ref?: React.Ref<HTMLInputElement>;
}): React.ReactElement {
return (
<label>
{label}
<input ref={ref} />
</label>
);
}NEVER: Use Context.Provider in React 19
import { createContext } from 'react';
const MyContext = createContext('default');
// BAD in React 19: Provider is deprecated
function BadApp(): React.ReactElement {
return (
<MyContext.Provider value="hello">
<Child />
</MyContext.Provider>
);
}
// GOOD in React 19: Use Context directly
function GoodApp(): React.ReactElement {
return (
<MyContext value="hello">
<Child />
</MyContext>
);
}react-core-architecture -- Core Type Reference
React Element
createElement
function createElement(
type: string | React.ComponentType<P>,
props: P | null,
...children: React.ReactNode[]
): React.ReactElement;| Parameter | Type | Description |
|---|---|---|
type | `string \ | ComponentType` |
props | `object \ | null` |
...children | ReactNode[] | Child elements, strings, numbers, null, or arrays |
Returns: Immutable ReactElement object:
interface ReactElement<P = any, T = string | React.ComponentType<P>> {
type: T;
props: P;
key: string | null;
ref: React.Ref<any> | null;
}---
createRoot (react-dom/client)
Signature
function createRoot(
domNode: Element | DocumentFragment,
options?: CreateRootOptions
): Root;CreateRootOptions
| Option | Type | Description |
|---|---|---|
onCaughtError | (error: Error, errorInfo: ErrorInfo) => void | Called when Error Boundary catches an error |
onUncaughtError | (error: Error, errorInfo: ErrorInfo) => void | Called when an error is not caught by any Error Boundary |
onRecoverableError | (error: Error, errorInfo: ErrorInfo) => void | Called when React recovers automatically from an error |
identifierPrefix | string | Prefix for useId() generated IDs; avoids conflicts with multiple roots |
Root Object
| Method | Signature | Description |
|---|---|---|
render | (reactNode: ReactNode) => void | Display React content in the root DOM node |
unmount | () => void | Destroy the rendered tree and detach React |
---
hydrateRoot (react-dom/client)
Signature
function hydrateRoot(
domNode: Element | Document,
reactNode: ReactNode,
options?: HydrateRootOptions
): Root;ALWAYS use hydrateRoot instead of createRoot for server-rendered HTML. Using createRoot discards all server-rendered markup.
---
ReactNode Types
| Type | Description | Example |
|---|---|---|
ReactElement | JSX element or createElement result | <div /> |
string | Text content | "Hello" |
number | Rendered as text | 42 |
boolean | Renders nothing | true, false |
null | Renders nothing | null |
undefined | Renders nothing | undefined |
ReactFragment | Multiple children without wrapper | <>...</> |
ReactPortal | Renders into a different DOM subtree | createPortal(child, container) |
Iterable<ReactNode> | Arrays and iterables of nodes | [<li />, <li />] |
---
Component Type Signatures
Function Component
type FC<P = {}> = (props: P) => ReactNode;
// Preferred: explicit function declaration with typed props
interface GreetingProps {
name: string;
age?: number;
}
function Greeting({ name, age }: GreetingProps): React.ReactElement {
return <h1>Hello, {name}{age ? ` (${age})` : ''}</h1>;
}Memo Component
const MemoizedComponent = React.memo<Props>(function MyComponent(props: Props) {
return <div>{props.value}</div>;
});Lazy Component
const LazyComponent = React.lazy<React.ComponentType<Props>>(
() => import('./HeavyComponent')
);---
Fiber Node Structure (Internal Reference)
These fields are internal implementation details and MUST NOT be accessed directly. Listed for architectural understanding only.
| Field | Type | Purpose |
|---|---|---|
tag | number | Fiber type (FunctionComponent=0, HostRoot=3, HostComponent=5, etc.) |
type | `Function \ | string \ |
stateNode | `DOM node \ | null` |
child | `Fiber \ | null` |
sibling | `Fiber \ | null` |
return | `Fiber \ | null` |
memoizedState | `Hook \ | null` |
memoizedProps | object | Props from the last completed render |
pendingProps | object | Props for the current in-progress render |
alternate | `Fiber \ | null` |
lanes | number | Priority lane bits for scheduling |
flags | number | Side-effect flags (Placement, Update, Deletion, etc.) |
Double Buffering
React maintains two fiber trees:
| Tree | Role |
|---|---|
| Current tree | Represents what is currently on screen |
| Work-in-progress tree | Being built during the render phase |
After commit, the work-in-progress tree becomes the current tree. The alternate pointer connects corresponding fibers between the two trees.
---
Reconciliation Heuristics
| Rule | Behavior |
|---|---|
Different type | Tear down old subtree, build new one from scratch |
Same type (host element) | Keep DOM node, update only changed attributes |
Same type (component) | Keep instance, update props, re-render |
key changed | Unmount old instance, mount new one (even if type matches) |
key stable | Reorder without unmounting (lists) |
No key on list items | Fall back to index-based matching (fragile, causes bugs) |
Priority Lanes (React 18+)
| Lane Category | Examples | Priority |
|---|---|---|
| Sync | flushSync() callbacks | Highest |
| Input | Discrete events (click, keydown) | High |
| Default | setState in event handlers | Normal |
| Transition | startTransition() updates | Low |
| Idle | requestIdleCallback-style work | Lowest |
Higher-priority lanes interrupt lower-priority rendering work, allowing React to stay responsive during expensive updates.
---
StrictMode API
import { StrictMode } from 'react';
// Wrap entire app (recommended)
root.render(
<StrictMode>
<App />
</StrictMode>
);
// Wrap specific subtree
function App(): React.ReactElement {
return (
<div>
<Header />
<StrictMode>
<main>
<NewFeature />
</main>
</StrictMode>
</div>
);
}| Props | Type | Description |
|---|---|---|
children | ReactNode | The subtree to wrap with strict checks |
No other props. No way to opt out components inside a StrictMode boundary.
react-core-architecture -- Working Examples
Basic Application Entry Point
React 18+ with createRoot
// main.tsx
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import App from './App';
const rootElement = document.getElementById('root');
if (!rootElement) throw new Error('Root element not found');
const root = createRoot(rootElement);
root.render(
<StrictMode>
<App />
</StrictMode>
);Multiple Roots (Partial React Integration)
// Mount React widgets into an existing non-React page
import { createRoot } from 'react-dom/client';
import { Navigation } from './Navigation';
import { Comments } from './Comments';
const navRoot = createRoot(document.getElementById('navigation')!);
navRoot.render(<Navigation />);
const commentsRoot = createRoot(document.getElementById('comments')!);
commentsRoot.render(<Comments />);---
React Element Creation
JSX vs createElement
// These are equivalent:
// JSX syntax
const element = <h1 className="greeting">Hello, world!</h1>;
// createElement syntax
import { createElement } from 'react';
const element = createElement('h1', { className: 'greeting' }, 'Hello, world!');
// Both produce this object:
// {
// type: 'h1',
// props: { className: 'greeting', children: 'Hello, world!' },
// key: null,
// ref: null
// }Component Elements vs Host Elements
import { createElement } from 'react';
interface GreetingProps {
name: string;
}
function Greeting({ name }: GreetingProps): React.ReactElement {
// Returns a HOST element (string type = DOM node)
return <h1>Hello, {name}</h1>;
}
// A COMPONENT element (function type = React component)
const componentElement = <Greeting name="Taylor" />;
// Equivalent: createElement(Greeting, { name: 'Taylor' })
// A HOST element (string type = DOM node)
const hostElement = <div id="container" />;
// Equivalent: createElement('div', { id: 'container' })---
Rendering Phases in Action
Pure Render Phase
interface CounterProps {
initialCount: number;
}
// CORRECT: Pure component -- same props always produce the same output
function Counter({ initialCount }: CounterProps): React.ReactElement {
const [count, setCount] = React.useState(initialCount);
// This function body is the "render phase" -- it MUST be pure
const doubled = count * 2; // Pure calculation -- OK
return (
<div>
<p>Count: {count} (doubled: {doubled})</p>
<button onClick={() => setCount(c => c + 1)}>Increment</button>
</div>
);
}Side Effects in the Correct Phase
import { useState, useEffect } from 'react';
interface User {
id: string;
name: string;
}
function UserProfile({ userId }: { userId: string }): React.ReactElement {
const [user, setUser] = useState<User | null>(null);
// Side effect in useEffect -- runs AFTER the commit phase
useEffect(() => {
let cancelled = false;
async function fetchUser(): Promise<void> {
const response = await fetch(`/api/users/${userId}`);
const data: User = await response.json();
if (!cancelled) {
setUser(data);
}
}
fetchUser();
// Cleanup runs on unmount or before re-running
return () => {
cancelled = true;
};
}, [userId]);
if (!user) return <p>Loading...</p>;
return <h1>{user.name}</h1>;
}---
Component Lifecycle Demonstration
Mount, Update, Unmount
import { useState, useEffect, useRef } from 'react';
function LifecycleDemo(): React.ReactElement {
const [count, setCount] = useState(0);
const renderCountRef = useRef(0);
// Increments on every render (mount + updates)
renderCountRef.current += 1;
// Runs on mount only (empty dependency array)
useEffect(() => {
console.log('MOUNTED');
return () => {
console.log('UNMOUNTED');
};
}, []);
// Runs on mount and every time count changes
useEffect(() => {
console.log(`Count updated to: ${count}`);
return () => {
console.log(`Cleaning up for count: ${count}`);
};
}, [count]);
return (
<div>
<p>Count: {count}</p>
<p>Render count: {renderCountRef.current}</p>
<button onClick={() => setCount(c => c + 1)}>Increment</button>
</div>
);
}Lifecycle sequence for the above component:
// Mount:
// 1. Component function called (render phase)
// 2. DOM inserted (commit phase)
// 3. "MOUNTED" logged
// 4. "Count updated to: 0" logged
// Click "Increment":
// 1. Component function called with count=1 (render phase)
// 2. DOM patched (commit phase)
// 3. "Cleaning up for count: 0" logged (previous effect cleanup)
// 4. "Count updated to: 1" logged (new effect runs)
// Unmount:
// 1. "Cleaning up for count: 1" logged
// 2. "UNMOUNTED" logged
// 3. DOM removed---
Composition Patterns
Children Composition (Preferred)
interface CardProps {
title: string;
children: React.ReactNode;
}
function Card({ title, children }: CardProps): React.ReactElement {
return (
<div className="card">
<h2>{title}</h2>
<div className="card-body">{children}</div>
</div>
);
}
function App(): React.ReactElement {
return (
<Card title="User Profile">
<p>Name: Taylor</p>
<p>Role: Developer</p>
</Card>
);
}Render Props Pattern
interface DataFetcherProps<T> {
url: string;
render: (data: T | null, loading: boolean) => React.ReactNode;
}
function DataFetcher<T>({ url, render }: DataFetcherProps<T>): React.ReactElement {
const [data, setData] = useState<T | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
let cancelled = false;
setLoading(true);
fetch(url)
.then(res => res.json())
.then((result: T) => {
if (!cancelled) {
setData(result);
setLoading(false);
}
});
return () => { cancelled = true; };
}, [url]);
return <>{render(data, loading)}</>;
}
// Usage
function UserList(): React.ReactElement {
return (
<DataFetcher<User[]>
url="/api/users"
render={(users, loading) => {
if (loading) return <p>Loading...</p>;
if (!users) return <p>No data</p>;
return (
<ul>
{users.map(u => <li key={u.id}>{u.name}</li>)}
</ul>
);
}}
/>
);
}Custom Hook Extraction (Best Practice)
interface UseFetchResult<T> {
data: T | null;
loading: boolean;
error: Error | null;
}
function useFetch<T>(url: string): UseFetchResult<T> {
const [data, setData] = useState<T | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
let cancelled = false;
setLoading(true);
setError(null);
fetch(url)
.then(res => {
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json();
})
.then((result: T) => {
if (!cancelled) {
setData(result);
setLoading(false);
}
})
.catch((err: Error) => {
if (!cancelled) {
setError(err);
setLoading(false);
}
});
return () => { cancelled = true; };
}, [url]);
return { data, loading, error };
}
// Usage -- clean and composable
function UserList(): React.ReactElement {
const { data: users, loading, error } = useFetch<User[]>('/api/users');
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error.message}</p>;
if (!users) return <p>No data</p>;
return (
<ul>
{users.map(u => <li key={u.id}>{u.name}</li>)}
</ul>
);
}---
Reconciliation and Keys
Stable Keys for Lists
interface TodoItem {
id: string;
text: string;
completed: boolean;
}
function TodoList({ items }: { items: TodoItem[] }): React.ReactElement {
return (
<ul>
{items.map(item => (
// ALWAYS use a stable, unique identifier as key
<li key={item.id}>
<span style={{ textDecoration: item.completed ? 'line-through' : 'none' }}>
{item.text}
</span>
</li>
))}
</ul>
);
}Key for Resetting Component State
function App(): React.ReactElement {
const [userId, setUserId] = useState('1');
return (
<div>
<button onClick={() => setUserId('1')}>User 1</button>
<button onClick={() => setUserId('2')}>User 2</button>
{/* Changing key forces React to unmount and remount the component,
resetting all internal state */}
<UserProfile key={userId} userId={userId} />
</div>
);
}---
React 19: ref as Prop
// React 18: requires forwardRef
import { forwardRef } from 'react';
const InputV18 = forwardRef<HTMLInputElement, { placeholder: string }>(
({ placeholder }, ref) => {
return <input ref={ref} placeholder={placeholder} />;
}
);
// React 19: ref is a regular prop
function InputV19({ placeholder, ref }: {
placeholder: string;
ref?: React.Ref<HTMLInputElement>;
}): React.ReactElement {
return <input ref={ref} placeholder={placeholder} />;
}React 19: Context as Provider
import { createContext, useContext } from 'react';
const ThemeContext = createContext<'light' | 'dark'>('light');
// React 18
function AppV18(): React.ReactElement {
return (
<ThemeContext.Provider value="dark">
<Page />
</ThemeContext.Provider>
);
}
// React 19: Context component IS the provider
function AppV19(): React.ReactElement {
return (
<ThemeContext value="dark">
<Page />
</ThemeContext>
);
}