2

I'm learning both React and functional programming at the same time. When I was learning about the concept of side effects, I feel that the definition of side effects are a slightly different in React and functional programming.

In functional programming, if a function has an internal state, making changes to that internal state is a side effect.

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.

Did I miss something? Or is the concept of side effects really different in React and functional programming? Thank you.

9
  • 1
    In react functional components, state is handled by react itself, not the component. Think of state as one of the parameters you're passing to your function and calling setState tells react to rerender the component with different props/state. State management is orthogonal to your components. I'm not sure if that qualifies as a side effect though :/ Commented Apr 19, 2022 at 18:56
  • @EmileBergeron Thank you for the link, but unfortunately I don't think it answers my question. My question is not about where to put side effects, but the definition of side effects. Commented Apr 19, 2022 at 19:37
  • @BrandonPiña Yes, the question I would like to make sure is exactly what you asked: does internal states qualified as side effects? Commented Apr 19, 2022 at 19:40
  • @EmileBergeron I'll go ahead and remove the part of mentioning setCount in my question because it's causing confusion. Sorry. Commented Apr 19, 2022 at 19:56
  • "In functional programming, if a function has an internal state" - in functional programming, a function doesn't have internal state? Can you clarify what you mean? Commented Apr 19, 2022 at 20:47

2 Answers 2

2

The mathematical definition of a function is a mapping from input to output. And nothing else. So (x) => x + 1 is a function. The output depends only on the input, not on the contents of some file system, or on a network connection, or on user input, or on a random number generator. "Side effects" are when a function deviates from this definition.

So a function () => Math.random() is not an actual "mathematical" function, since you can pass it the same inputs (namely, none of them) and get different outputs. Functional languages get around this by saying the random state is really just another parameter. So in Haskell, we would do something like this: (gen) => gen.random(), where gen is the random number generation state. Now this is a pure function with no side effects. If we give it the same input (namely, the same generator state), it'll give us the same output consistently. This is the philosophical viewpoint functional programming is coming from.

React's notion of "side effects" is meant to prevent things outside of React's control. React wants you to move all of your (mathematical) side effects into the internal state, which React controls. That doesn't make your function any more of a mathematical function; the definition of "function" remains the same. It just means that React can see those side effects.

Sign up to request clarification or add additional context in comments.

2 Comments

In other words, is the definition of side effects in React "things that's out of control of React"?
I think that's an accurate description. I would argue that any pure function (in the mathematical sense of "side effect free") is a "pure" function (in the React sense of "side effect free"), but the converse is not true.
0

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.

1 Comment

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.