Note: I am not a mathematics specialist. This essay is an attempt to use the vocabulary of category theory and algebraic effects to understand React, so please read it as a proposed model rather than a rigorous theorem. If you notice a mathematical mistake, I would be happy to be corrected.
Introduction
"UI = f(State)" - this is one of the most widely circulated equations in discussions about React. At first glance, the equation looks simple and seems to capture the essence of React: if the state is determined, the UI is determined.
However, when we read this equation carefully in mathematical terms, several points need clarification.
What is this
f? Is it a "function" in the mathematical sense?If it is a function, in which category is it a morphism?
Are React's notions of "pure" and "idempotent" the same as the mathematical ones?
In this essay, I will use the framework of category theory and algebraic effects to try to model React Components more precisely.
The Mathematical Meaning of "Pure"
Let us clarify the mathematical meaning of the word "pure," which is often used in React documentation and discussions.
In mathematics, a pure function is a mapping with referential transparency. In the language of category theory, it corresponds to a morphism in the category of sets, Set:
This morphism f satisfies the following:
It always returns the same output for the same input (deterministic)
It does not read from or write to external state (no side effects)
It does nothing other than compute a value (no observable effects)
Now consider a React Function Component:
function Greeting({ name }) {
const [count, setCount] = useState(0);
const theme = useContext(ThemeContext);
useEffect(() => {
document.title = `Hello, ${name}`;
}, [name]);
return (
<h1 style={{ color: theme.primary }}>
Hello, {name} ({count})
</h1>
);
}
This Component:
reads internal state and receives an updater through
useStatereads external state from the component tree through
useContextschedules a side effect through
useEffect
This is not a morphism in Set. It reads things other than its argument (props), and it does things other than compute a pure value.
A React Component is not a "pure function" in the mathematical sense.
Then why does React call Components "pure"? Because React uses the word "pure" in a different sense. In React, "pure" means:
Having no observable side effects during rendering - more precisely, behaving correctly inside React's effect system.
This is a much weaker condition than mathematical purity, and confusing these terms leads to a fundamental misunderstanding.
The Original Meaning of "Idempotent"
React's documentation says the following (Components and Hooks must be pure):
"Components must be idempotent - React components are assumed to always return the same output with respect to their inputs - props, state, and context."
What does "idempotent" mean mathematically here?
Idempotence in Mathematics
The definition of an idempotent element in algebra is:
Definition (idempotent element). In a monoid
(M, ·, e), an elementa ∈ Mis idempotent if it satisfiesa · a = a.
Extending this to functions:
Definition (idempotent endomorphism). In a category
𝒞, a morphisme: A → Ais idempotent if it satisfiese ∘ e = e.
These two definitions follow Eric W. Weisstein, "Idempotent," MathWorld for idempotent elements and Emily Riehl, Category Theory in Context, Example 3.2.14 for idempotent endomorphisms.
Concrete examples:
The absolute value function is idempotent:
||x|| = |x|A projection matrix
Pis idempotent:P^2 = PMath.flooris idempotent:Math.floor(Math.floor(x)) === Math.floor(x)
What matters here is that idempotence is a property of an endomorphism f: A -> A.
Applying f once more to the output of f does not change the result - this is the original meaning of idempotence.
React's "Idempotence" Is Not Mathematical Idempotence
When React says a Component is "idempotent," it means "it returns the same output for the same input." Mathematically, this is not idempotence. It should be called determinism or well-definedness.
Why?
The type of a Component is
Props -> VDOM, andProps != VDOM. In other words, it is not even an endomorphism.To verify
f ∘ f = f, we need to pass the output offback intofas input. But passing a Component's output (VDOM) into the Component's input (Props) has no meaning.What React is saying is "
f(x) = f(x)(the same input gives the same output)," not "f(f(x)) = f(x)."
f(x) = f(x) is a property every function should satisfy by definition, and it is not something we would go out of our way to call "idempotence." More precisely, what React intends is probably that "even if Hooks have internal state, the Component returns the same VDOM for the same tuple of (props, state, context)." But this is a "deterministic function," not an "idempotent" one.
The Rendering Operation Can Be Modeled as Idempotent
However, if we shift our point of view, React does contain an operation that can be modeled as idempotent.
Fix a state s, set aside user Effects and external I/O for the moment, and look only at the DOM state.
Then consider the composition of rendering and committing as a single operation:
Then:
Applying render and commit twice for the same state yields the same DOM state as applying them once, if we look only at the DOM state. If there is no diff, the second application can be treated as a no-op. This is idempotence when viewed as a DOM state transformation.
In other words, what should be treated as idempotent in React is not the Component function itself, but the idealized render-and-commit operation u_s.
This distinction is decisive.
React Fiber and Algebraic Effects
Now let us turn to React's internal structure.
React Fiber - React's core runtime - has features that resemble Algebraic Effects. Dan Abramov also introduces algebraic effects as a mental model for thinking about some things React does (Dan Abramov, "Algebraic Effects for the Rest of Us", 2019). The same article explicitly calls the connection to React "a stretch," says Suspense is not an algebraic effect per se, and notes that JavaScript cannot really resume execution later.
What Are Algebraic Effects?
Algebraic effects are a mathematical framework for separating computation from effects (side effects). The general explanation in this section follows Plotkin and Power, "Algebraic Operations and Generic Effects" (2003).
The basic structure consists of three elements:
Effect signature: the set of available operations
Computation: a program that may call effect operations
Handler: an interpreter that gives meaning to effect operations
This is not React's actual implementation, but pseudocode for explaining the correspondence:
// Effect signature
effect GetState : Unit -> State
effect SetState : State -> Unit
effect ReadContext: Key -> Value
effect Suspend : Promise<A> -> A
effect Throw : Error -> bottom
// Handler (= React Fiber runtime)
handler ReactFiber {
return vdom -> vdom
GetState(_, resume) -> resume(currentFiber.memoizedState)
SetState(newState, resume) -> enqueueUpdate(newState); resume(unit)
Suspend(promise, retryRender) -> showFallback(); promise.then(() -> retryRender())
Throw(error, _) -> propagateToErrorBoundary(error)
}
Here the relationship between the React Function Component written by the user and the React Fiber runtime can be mapped as follows:
| Role in algebraic effects | Correspondence in React | |
|---|---|---|
| Effect signature | Declaration of available operations | Hooks API (useState, useContext, use, ...) |
| Computation | Program that calls effect operations | User-written Function Component |
| Handler | Interpreter that gives meaning to operations | React Fiber runtime |
In this model, React Hooks can be read as effect operations:
| Hook | Effect operation | Handler |
|---|---|---|
useState |
GetState / SetState |
Fiber's state queue |
useContext |
ReadContext |
Provider chain lookup |
use(promise) |
Suspend |
Suspense boundary |
throw error |
Throw |
Error boundary |
useEffect |
ScheduleEffect |
Effect queue in commit phase |
A user-written Component can be viewed as an effectful computation, and React Fiber as its handler (interpreter).
Function Composition in the Kleisli Category
The "function composition" implicitly assumed by "UI = f(State)" also needs to be examined mathematically.
Kleisli Category
Given a monad T on a category 𝒞, we can construct the Kleisli category 𝒞_T.
Here I mean a categorical monad: an endofunctor T: 𝒞 → 𝒞 together with natural transformations η: Id ⇒ T (unit) and μ: T² ⇒ T (multiplication), forming a triple (T, η, μ) that satisfies the monad laws. For the definitions of monad and Kleisli category, see Emily Riehl, Category Theory in Context, Definition 5.1.1 and Definition 5.2.10:
Objects: the same as
𝒞Morphisms: a morphism
A -> Bin𝒞_Tis a morphismA -> T(B)in𝒞Composition: for
f: A -> T(B)andg: B -> T(C), their Kleisli composition is:
That is:
A React Component Is a Kleisli Morphism
From this point on, this is not an official formal semantics of React.
It is a model that gathers React's different effects into one abstract effect monad.
Let R be that hypothetical React effect monad.
Then the type of a React Component is:
This can be modeled as a morphism Props -> VDOM in the Kleisli category Set_R.
Reading it as a Kleisli morphism (an effectful computation), rather than as a Set morphism (a pure function), is closer to React's behavior.
Component Composition
Consider component composition in JSX:
function Parent({ data }) {
const processed = use(processData(data));
return <Child items={processed} />;
}
If we abstract this dependency, we can sketch it as Kleisli composition:
Here ∘_R is not ordinary composition ∘ in Set, but Kleisli composition for the abstracted React effect monad.
Because it passes through a call to use - that is, an effect corresponding to Suspend - it is hard to express as pure function composition.
In other words, if we include Hooks and Suspense, "function composition" in React cannot be fully captured by ordinary function composition alone. This is another reason why "UI = f(State)" is not naively correct.
Monad Bind and do Notation - What a Component Body Really Is
Now that we understand Kleisli composition, let us dig one layer deeper into what is happening inside the function body of a Component.
For a monad T, the bind operation (written >>= in Haskell) has the following type:
It is the operation that "takes the result of an effectful computation and passes it to the next effectful computation." Haskell's do notation is syntactic sugar for writing chains of bind in a readable way:
-- do notation
do
state <- getState
ctx <- readContext themeCtx
pure (view state ctx)
-- Desugared (chain of bind)
getState >>= \state ->
readContext themeCtx >>= \ctx ->
pure (view state ctx)
Now look at the body of a React Component:
function Component(props) {
const [state, setState] = useState(init); // <- bind: GetState >>= \state ->
const ctx = useContext(ThemeCtx); // <- bind: ReadContext >>= \ctx ->
return <View state={state} ctx={ctx} />; // <- pure: return (View state ctx)
}
The body of a React Component can be read as something close to do notation for the React effect monad.
In this model, each Hook call can be regarded as corresponding to bind (>>=):
Execute an effect operation (obtain
T(A))Bind its result to a variable (extract
A)Pass it to the rest of the computation (apply
A -> T(B))
The correspondence looks like this:
| Haskell do notation | React Component body |
|---|---|
x <- action |
const x = useXxx(...) |
pure expr |
return <JSX /> |
action1 >> action2 |
Hook call whose return value is unused (useEffect(...)) |
| Chain structure of bind | Order of Hook calls |
Rules of Hooks Require the Bind Structure to Be Static
Here, React's Rules of Hooks can be read inside this model as follows:
Do not call Hooks inside loops, conditions, or nested functions
Hooks must always be called at the top level of a Function Component
In this model, the bind structure of the do notation must be static across renders.
In Haskell do notation, the following kind of code may type-check but can be semantically problematic in some contexts:
do
x <- action1
if condition
then do { y <- action2; pure (f x y) } -- two binds
else pure (g x) -- one bind
The React equivalent is forbidden:
// NG: Hook inside a conditional branch
function Component({ condition }) {
const x = useState(0);
if (condition) {
const y = useContext(Ctx); // <- Rules of Hooks violation
return <A x={x} y={y} />;
}
return <B x={x} />;
}
Why? React Fiber stores the order of Hook calls (= the order of the bind chain) as a linked list. If the number or order of binds changes from render to render, Fiber cannot correctly line up the previous render's result with the current Hook calls.
So the Rules of Hooks can be read as requiring the bind structure in the do notation of the React effect monad to be static (fixed). This is an implementation constraint of React, and inside this model it can be interpreted as a structural consistency requirement for monadic computation.
A related experimental implementation is ubugeeei/mreact. It models React Hooks as an indexed monad in Haskell, representing the Hook call order as a type-level list so that the Rules of Hooks can be handled by the type checker.
use and Suspense - A Concrete Form That Resembles Algebraic Effects
The use Hook and Suspense are where the algebraic-effects-like behavior in React is easiest to see.
However, this does not mean that React's implementation has real algebraic effects.
function UserProfile({ userId }) {
const user = use(fetchUser(userId));
return <div>{user.name}</div>;
}
function App() {
return (
<Suspense fallback={<Loading />}>
<UserProfile userId={1} />
</Suspense>
);
}
We can describe this mechanism with the mental model of algebraic effects:
1. use(promise) corresponds to performing an effect operation:
If the Promise is unresolved, the public React API says that the Component suspends (use integrates with Suspense).
This can be read as a non-local control flow resembling a "call to an operation" in algebraic effects.
2. Suspense corresponds to an effect handler:
Suspense handles the suspension, displays the fallback UI, and retries rendering after the Promise resolves.
3. On re-render, use(promise) returns the resolved value:
This resembles the algebraic-effects operation of "passing a value to the continuation and resuming it." However, React is not actually saving the JavaScript execution stack and resuming from that point. It re-renders the Component tree after the Promise resolves. This point follows the React Suspense documentation and Dan Abramov's caveat in "Algebraic Effects for the Rest of Us".
In pseudo handler notation:
handle(UserProfile(userId)) with {
return vdom -> vdom
Suspend(promise, retryRender) ->
display <Loading />;
await promise;
retryRender() // <- re-render, not a real continuation resume
}
What is especially important here is that use can suspend a Component's render, and React can re-render it later.
An ordinary JavaScript function runs to completion once called.
With async / await, execution can continue from an await point, but React Components are not written as async function.
Even so, React uses Suspense boundaries and re-rendering to create the experience that it "waited there and continued once the value arrived."
This is what makes the behavior reminiscent of algebraic effects (or delimited continuations).
The Mistake of Client-Server "Isomorphism"
There are terms such as "Isomorphic JavaScript" and "Universal JavaScript." They are also often used in the context of Server Components, but is this "isomorphism" mathematically correct?
Isomorphism in Category Theory
Definition (isomorphism). In a category
𝒞, a morphismf: A → Bis an isomorphism if there exists a morphismg: B → Asuch thatg ∘ f = id_Aandf ∘ g = id_B.
This definition follows Emily Riehl, Category Theory in Context, Definition 1.1.10.
An isomorphism means a reversible transformation that preserves structure.
If A and B are isomorphic, then they cannot be distinguished category-theoretically.
Server Rendering and Client Rendering Are Not Isomorphic
Consider server rendering and client rendering as functors:
To begin with, HTML != DOM.
Since the output categories are different, there is no room to construct an isomorphism between F_S and F_C.
A transformation from an HTML string to the DOM (parsing) exists, but the reverse transformation (DOM -> HTML) is serialization. The composition of these two is not necessarily the identity morphism, because event handlers, internal state, closures, and so on are lost.
A More Accurate Description: Different Effect Handlers
In the framework of algebraic effects, server rendering and client rendering can be understood as different handlers for the same effectful computation:
As the React documentation explains, the difference between Server Components and Client Components appears as a difference in execution environment and available APIs. In this essay's model, we can understand that as a difference in available effect signatures:
Server Component: can directly use server-side operations such as DB queries and file system access. Cannot use many Hooks such as
useStateanduseEffect.Client Component: can use client-side APIs such as
useStateanduseEffect. Cannot directly bring server-only code into the Client module subtree.
This is not an isomorphism. It should be understood as an inclusion relation or subtyping between effect signatures.
The shared portion works as Components that can become either Server Components or Client Components depending on usage. Server-only operations are available only on the server side, and client-only operations are available only on the client side. This is not "isomorphism," but compatibility based on partial overlap between effect signatures.
Conclusion: Rewriting UI = f(State)
Let us summarize the discussion so far.
Problems with the Naive Equation
Problems with this equation:
fis not a pure function - it executes effects through HooksThe word "idempotent" is misused - the Component function is not mathematically idempotent (the rendering operation can be modeled as idempotent)
Composition cannot be captured by ordinary function composition alone - it can be modeled as Kleisli composition
Client-Server is not an isomorphism - they can be modeled as different effect handlers
A More Accurate Description
In this essay's model, a React Component can be expressed as a Kleisli morphism of the effect monad R:
UI generation can be expressed as interpretation by an effect handler h:
Here:
Component(Props): R(VDOM)is the description of the effectful computation written by the userh: R(VDOM) -> DOMis a model of the interpretation by the React Fiber runtime
And if we set aside user Effects and external I/O and view it as a DOM state transformation, the rendering operation u_s = h(render(s)) can be modeled as an idempotent endomorphism on the DOM:
is useful as an educational intuition, but if we look at React's behavior more carefully, it needs some qualification.
A React Component is not a pure function in the mathematical sense. It can be modeled as an effectful computation in an algebraic effect system, and React Fiber can be read as its handler (interpreter).
With this understanding, React's "rules," the behavior of Hooks, the mechanism of use and Suspense, and the design principles of Server Components become easier to understand in a unified way - not as scattered bits of knowledge, but as things that can be seen through the single framework of algebraic effects.