Home > Enterprise >  Is the definition of side effects in React the same as in functional programming?
Is the definition of side effects in React the same as in functional programming?

Time:04-20

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.

CodePudding user response:

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.

CodePudding user response:

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.

  • Related