From what I gather, React initially renders a component tree with starting values. When particular values or events occur, this triggers a rerender after running the reconciliation algorithm to see what changed, marking them for a rerender.
High-level Rendering Overview
Every component exports a render function that is compiled JSX. Components are rendered from the root on down through its children using provided props
DIRECT RENDER EVENTS
- Components are rerendered when any of its props change. All child components are also rerendered.
- Components in an array are rerendered when their unique key prop changes.
- Components that refer to state or context in their render functions are also rerendered when they change through the
useState
anduseContext
hooks.
INDIRECT RENDER CHANGES
- Hooks that use dependencies (e.g.
useEffect
) will run whenever the dependency changes. The body of the hook can then change state or context to affect the component. - Contexts that contain state hooks (e.g.
useState
) provides a way for components to use them. The context value doesn't change, and neither do its properties.
CHANGE DETECTION ALGORITHM
- React uses simple object equality (by reference) and shallow comparisons (by value, no nested objects) to detect changes.
Sources of Refresh Events
The tables below are based on Claude Sonet query, "Provide a list of React's refresh triggering mechanisms for the built-in hooks, props for functional components." so there's a good chance it's not completely right.
In general, the refresh process starts with the component that triggered it and its subtree of components also rerenderModified by memoization through useMemo
..
Mechanism | Refresh Behavior |
---|---|
State Change | Component refreshes when internal state is updated via setState or Hook state setters |
Props Change | Component refreshes when received props change (props with objects use reference equality) |
Parent Render | Component refreshes when parent component renders (unless wrapped in memo) |
Context Change | Component refreshes when the value of the context contains useState getters |
Key Change | Complete unmount/remount cycle when key prop changes |
React.memo | Prevents refresh when props actually change |
Reducer Updates | Component refreshes when a reducer dispatch function is called with an action |
Force Update | Class components refresh when forceUpdate() is called |
External Store | Component refreshes when subscribed external store changes via useSyncExternalStore |
Concurrent Features | Updates from useDeferredValue or useTransition completing their work (?) |
Error Boundaries | Components refresh when an error is caught or reset |
Suspense Resolution | Components refresh when suspended data becomes available |
Note that React compares objects consistently throughout its system using reference equality.
Refresh-related Hooks
Hook | Refresh Trigger Mechanism |
---|---|
useState |
Component refreshes when state setter function is called with a new value (reference or primitive) |
useReducer |
Component refreshes when dispatch function is called, resulting in a new state |
useContext |
Component refreshes when the context value changes (based on reference equality) |
useCallback |
Returns memoized function; dependencies array change creates new function reference |
useMemo |
Returns memoized value; recalculates only when dependencies change |
useDeferredValue |
Creates deferred version of value that executes asynchronously |
useSyncExternalStore |
Subscribes to external store; refreshes when store changes |
Other Hooks
Hook | Effect Trigger Mechanism |
---|---|
useEffect |
No direct refresh; triggers side effects after render |
useLayoutEffect |
No direct refresh; triggers synchronous side effects before browser paint |
useRef |
No refresh when .current property changes; maintains reference across renders |
useImperativeHandle |
No direct refresh; customizes instance value exposed to parent components |
useDebugValue |
No refresh; used for displaying custom hook values in React DevTools |
useTransition |
No direct refresh; marks state updates as transitions to avoid blocking UI |
useId |
No refresh; generates stable, unique IDs across server/client |
Reconciliation Algorithm
React's reconciliation process follows these sequential steps:
-
Tree Comparison: React compares the current virtual DOM tree with the new one generated after state or prop changes.
-
Element Type Evaluation: React first checks if the element type has changed. If the type differs (e.g., from
div
tospan
), React discards the old subtree and builds a new one. -
Key-Based Reconciliation: For lists of elements, React uses the
key
prop to track which items have been added, removed, or repositioned rather than re-rendering the entire list. -
Component Instance Preservation: When element types match, React updates only the props of the existing component instance, maintaining internal state.
-
DOM Update Batching: Once differences are identified, React batches DOM updates to minimize reflow and repaint operations.
Appendix: Writing Custom Hooks
import { useState, useEffect } from 'react';
function useCustomHook(initialValue) {
// State management
const [value, setValue] = useState(initialValue);
// Side effects
useEffect(() => {
// Effect logic
return () => {
// Cleanup logic (optional)
};
}, [dependencies]);
// Additional logic or transformations
// Return values and/or functions
return { value, setValue };
}
export default useCustomHook;
Note that the return value is not limited to the patterns established by useEffect
or useState
; you can return anything that makes use of the built-in hooks to do React-related stuff.
Appendix: Difference Between Class and Functional Components
Class Components
Class Components use lifecycle methods to explicitly orchestrate lifecycle events:
- The constructor receives props from
<Component prop1 prop2 />
- State is initialized in the constructor by directly setting
this.state
. Changes afterwards must be done throughthis.setState()
so reconciliation of changes can run. - Class methods
componentDidMount()
andcomponentDidUnmount()
for initialization and garbage collection - The
render()
method returns JSX that made use ofthis.state
and any passed props.
The source of the render trigger can be local or from a parent:
- local
this.setState()
will trigger a local rerender based on what's in therender()
function. - When the parent rerenders due to state or prop change, all its children also rerender.
Functional Components
Functional Components, by comparison, use an implicit lifecycle, and rely on hooks to trigger rerenders.
- The function receives a props object
- The function returns JSX as its render function, which as in Class Components will references to any props that are used.
- State is provided through the
useState
hook, which provides the syntactic sugar to make state look like a regular variable reference, and providing a separate setter function that serves as the trigger for reconciliation - Lifecycle mount/unmount behavior is encapsulated by the
useEffect
hook, which can return a "cleanup" function to run when the function is unmounted. There can be more than one such effect hook.