How pure should a function be when writing in React?

443 views Asked by At

In functional programming, a pure function returns the same value for the same arguments.

I'd like to hear tips for writing in React sometime. I definitely do a lot of thinking at some point. For example, the onClickHandler in the code below is not a pure function because it depends on an external state change.

const { useState } = require("react")

const Example = () => {
    const [list, setList] = useState(["a", "b", "c", "d", "e"])

    // Delete item from list, when button is clicked
    // This function is non-puer because it uses external state (list, setList)
    const onClickHandler = (e) => {
        const newList = list.filter(item => item !== e.target.innerText)
        setList(newList)
    }
    
    return (
        <div>
            {list.map((item, index) => {
                return (
                    <div key={index}>
                        <button onClick={onClickHandler}>{item}</button>
                    </div>
                )
            } 
        )}
        </div>
    )
}

export default Example
  • In this case, is it good to write these codes as pure functions?
  • If not, should it be written as a pure function only at the component level?

I want to hear from programmers who are interested in React.

2

There are 2 answers

0
geoffrey On

What side-effects are there when you have a component with a click handler?

  • You have the action of appending/updating HTML elements to the DOM
  • you have the action of firing an event when the user interacts with it
  • and you have the action of mutating state.

Which one of these side-effects do you actually manage yourself when you use something like Redux for example? None of them.

A component which does not close over mutable free variables and merely describes the creation of DOM nodes without controlling what should be done with them or with their events when they fire, is pure.

The way you use something like Redux in a functional way is that your click handler should only send a signal to Redux saying "I have been pressed, and here are some contextual infos like the cursor coordinates, etc.". It is another piece of code somewhere else which decides how this event will affect the state, and when you write that external piece of code you don't decide how and when it will be executed either, and you won't even mutate the state yourself.

It is React which appends and updates nodes in the DOM, it is the browser which fires events, it is Redux which updates the state. From the point of view of your pure functional component, there are only inputs parameters and an output which is a description of an action, but is not an action itself. It is a pure function.

When you write functional code you very often voluntarily loose control over the execution by letting a framework manage all the side-effects in a predictable way, so that your code remains pure and declarative.

The paradigm shift is that, instead of having every component handle its state independently and collaborate with other components, like cells in an organism, there is a coordinator which knows about the whole state of the world, receives orderly signals about what happens in the app and takes all the decision regarding the creation of a new, updated but snapshot isolated, state of the world. This new state is then passed to the render function and the cycle starts again, with state flowing in a single direction.

3
Josh Kelley On

The updated React docs describe "purity" as "always return the same JSX given the same inputs." (I would also add that components' render functions shouldn't have externally visible side effects.)

This isn't quite the same as a purely mathematical or functional programming definition of purity: In your example, each time you call Example, the onClick handler that's passed to the DOM will be a different function instance, and the useState hook gives the possibility of state and mutation. However, it meets React's expectations:

  • A major purpose of hooks is to allow for side effects and state, so that's okay.
  • Even if the specific onClick function changes, the behavior ("this node responds to a click event and does XYZ") is the same.

If you did violate React's expectations (by having side effects or by rendering different JSX), then bugs are unlikely.

Beyond that, taking a functional programming style approach and using pure functions can make code more maintainable and can fit better with React's conventions and ecosystem. For example, in your Example:

  • setList is guaranteed not to change, so it's reasonable to not consider it as an external state dependency for onClickHandler.

  • onClickHandler can use an updater instead of directly depending on the list state. As explained in the React docs, this isn't required, but it can be helpful (especially once you get into effects, callbacks, and more complex state updates.)

    const onClickHandler = (e) => {
        setList(list => list.filter(item => item !== e.target.innerText))
    }