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.

johnlinp
  • 853
  • 6
  • 22
  • 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 :/ – Brandon Piña Apr 19 '22 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. – johnlinp Apr 19 '22 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? – johnlinp Apr 19 '22 at 19:40
  • @EmileBergeron I'll go ahead and remove the part of mentioning `setCount` in my question because it's causing confusion. Sorry. – johnlinp Apr 19 '22 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? – Bergi Apr 19 '22 at 20:47
  • "*the definition of side effects*" - what definitions have you encountered? Can you quote and/or link them, please? – Bergi Apr 19 '22 at 20:57
  • @Bergi It's true that pure functions don't have internal states. However, a functional programming language may have impure functions, so it's possible for an impure function to have internal states. – johnlinp Apr 19 '22 at 21:04
  • @Bergi I didn't find any good definition of React's side effects. Therefore I asked this question. – johnlinp Apr 19 '22 at 21:05
  • Internal state is a bit of a misnomer for functional components because the state is persisted in react itself, not the component. However useState is usually used *alongside* side effects like user interaction/network requests/timers. If useState was by itself in a react component without any other side effects it wouldn't do very much – Brandon Piña Apr 19 '22 at 21:34

2 Answers2

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.

Silvio Mayolo
  • 62,821
  • 6
  • 74
  • 116
  • In other words, is the definition of side effects in React "things that's out of control of React"? – johnlinp Apr 19 '22 at 21:00
  • 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. – Silvio Mayolo Apr 19 '22 at 21:31
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.

Bergi
  • 630,263
  • 148
  • 957
  • 1,375