Yes, both usages of the term "side effect" mean the same thing.
However, when an internal state is created by useState
within a React function component, modifying that internal state doesn't seem to be a side effect.
I would argue that calling useState
does not create internal state, and certainly that doesn't modify it.
It's a pity that function components are not truly pure functions, even though they're sometimes marketed as such. (At least they're officially called "function components" - constrasted to "class components" -, not "functional components"). They do rely a lot on global state, namely React
setting up the context for the evaluation of the component function for rendering it. This is why you cannot just call them like Counter()
, you have to use ReactDOM.render(<Counter>, …)
. A component such as
function Counter(props) {
const [count, setCount] = useState(0);
function increment() {
setCount(c => c+1);
}
return <div>
<p>Count: {count}</p>
<button onClick={increment}>+1</button>
</div>;
}
might better be written as
const key = Symbol()
function Counter(__opaqueReactContext, props) {
const [count, setCount] = __opaqueReactContext.useState(key, 0);
function increment() {
setCount(c => c+1);
}
return <div>
<p>Count: {count}</p>
<button onClick={increment}>+1</button>
</div>;
}
(The key
is used to identify the first useState
call and separate it from other useState
calls in the component, in reality React just counts the invocations and complains if you don't follow the "rules of hooks")
to be pure, but React doesn't want to hand out an __opaqueReactContext
that a) is not immutable for efficiency reasons and b) might be stored/references by badly written components and c) is not ergonomic to write.
Still, this is how you should think of a component as a pure function: the return value depends only on the argument values. There are no side effects during the evaluation of the function, React can (and in strict mode actually does for verification) run it multiple times and will get the same result (which is just an immutable description of elements to be rendered, nothing stateful either).
Now, where are the effects happening then, how does our application keep state? It somehow has to modify the DOM to be useful, and that's what ReactDOM.render
does. It will create and manage the state, in a complex tree hierarchy of component states. All the effects are under React's control here. This includes procedures (imperative code) that the user wrote, such as the increment
click handler in the above example, or a useEffect
callback. React decides when to execute them, and if they run an effect (such as calling setCount
), it will adjust its component model and run the "pure" component functions to rerender - with a different __opaqueReactContext
.