React Stateful vs Stateless Components: Two Explanations Quiz
Two explanations of the stateful/stateless (smart vs presentational) split, plus companions on hooks-era state in function components and on testability trade-offs.
929 views
22
Define a stateful (smart) component and a stateless (presentational) component, and explain why this split is a useful design boundary even in a hooks-only codebase.
Examples
Example 1:
Input: a component that owns a counter value and renders the count plus +/- buttons
Output: stateful (smart): it owns state and behavior.
Explanation: this component holds the counter in useState and decides how the user interactions mutate it.Example 2:
Input: a component that takes `count` and `onIncrement` as props and renders them
Output: stateless (presentational): no state, no behavior of its own, easy to reuse.
Explanation: it is a pure function of its props; render-only.import { useState } from 'react';
// presentational: pure function of props
function CounterView({ count, onInc, onDec }) {
return (
<div>
<button onClick={onDec}>-</button>
<span>{count}</span>
<button onClick={onInc}>+</button>
</div>
);
}
// smart: owns state, plugs into the presentational piece
function Counter() {
const [count, setCount] = useState(0);
return (
<CounterView
count={count}
onInc={() => setCount((c) => c + 1)}
onDec={() => setCount((c) => c - 1)}
/>
);
}
Compare a class component with this.state to a function component with useState. List concrete advantages function components have today (less code, no this binding, easier to test).
Examples
Example 1:
Input: same counter implemented as a class and as a function
Output:
class version has constructor, super(props), this.state, this.setState, render method, and method binding.
function version has one useState call and one onClick arrow.
Explanation: function components compose smaller and have no `this`, so refactoring is mechanical.import React, { useState } from 'react';
// class form
class ClassCounter extends React.Component {
constructor(props) {
super(props);
this.state = { n: 0 };
this.inc = this.inc.bind(this);
}
inc() { this.setState({ n: this.state.n + 1 }); }
render() {
return <button onClick={this.inc}>{this.state.n}</button>;
}
}
// function form
function FnCounter() {
const [n, setN] = useState(0);
return <button onClick={() => setN((v) => v + 1)}>{n}</button>;
}
Show that a function component without useState is effectively stateless. What does "stateless" mean precisely in a hooks-era codebase, and is a component using useRef for a mutable timer id still stateless?
Examples
Example 1:
Input:
function Greeting({ name }) { return <h1>Hello {name}</h1>; }
Output: stateless (no useState, no useReducer, no useRef-tracked behavior).
Explanation: output depends only on props.Example 2:
Input: a component that uses useRef to hold a timer id across renders
Output: pragmatically stateful (the ref is hidden internal state; output of effects depends on it across renders).
Explanation: "stateless" is about whether the component has observable internal state, not whether it has any local variables.import { useEffect, useRef } from 'react';
function Greeting({ name }) {
return <h1>Hello {name}</h1>;
}
function AutoRefresher({ onTick }) {
const id = useRef(null);
useEffect(() => {
id.current = setInterval(onTick, 1000);
return () => clearInterval(id.current);
}, [onTick]);
return null;
}
List four testability and reuse advantages of keeping presentational components stateless, and one situation where you would intentionally co-locate state into the leaf component anyway.
Examples
Example 1:
Input: a presentational <Badge tone="warning">Stale</Badge>
Output: testable by rendering with a few prop combinations; no state to seed; deterministic snapshots.
Explanation: the only variables are the props you pass; no effects, no side channels.Example 2:
Input: a <Disclosure> that animates open/closed and is used in 50 places
Output: co-locating the open state inside the component is fine, because no caller cares about intermediate animation state.
Explanation: state belongs as low in the tree as the consumers allow.// presentational - easy to test, easy to reuse
function Badge({ tone, children }) {
return <span data-tone={tone}>{children}</span>;
}
// co-located state when no one outside needs to see it
import { useState } from 'react';
function Disclosure({ summary, children }) {
const [open, setOpen] = useState(false);
return (
<div>
<button onClick={() => setOpen((o) => !o)}>{summary}</button>
{open && <div>{children}</div>}
</div>
);
}
