Blog Hero Background

Reconciling the useEffect Tree


User Avatar
Ryan FlorenceSay hi on Twitter
March 08, 2019
ReactHooksuseEffect

Upcoming Workshops

I wanna talk a little bit about useEffect in React because I absolutely love it and I've noticed a bit of confusion about it in the community.

Side Effects

useEffect is a new way in React to describe side effects. What's a side effect? Well, if you've got some time this weekend, why don't you give this wikipedia article a read. In short, a side effect is anything that a function does besides returning a value. Here's a function without side effects.

let add = (x, y) => x + y

And it's nice because we can plan on it not doing anything unexpected:

// prettier-ignore
[1, 2, 3].map(v => add(v, 1))
// [2, 3, 4]
[1, 2, 3].map(v => add(v, 2))
// [3, 4, 5]

Here's one with a side effect:

let add = (x, y) => {
Array.prototype.map = () => '🗺'
return x + y
}

And it's funny cause now:

// prettier-ignore
[1, 2, 3].map(v => add(v, 1))
// [2, 3, 4]
[1, 2, 3].map(v => add(v, 2))
// "🗺"

Get it? The first map with calls add which has a "side effect" and changes the semantics of the map function for the next time it's used. The next map is now weird and buggy.

So anyway, usually side effects are actually useful. And if you think about it, if a program had nothing but functions without side effects nothing would happen. Eventually you need a side effect for a program to have any utility at all. In the first programs most of us write we console.log()'d or print'd.

In React, the primary side effect is rendering to the target platform, like DOM elements.

React Manages Element Side Effects

We don't have to manage the primary side effect in React because that's the whole point of the library. We hand our app off to ReactDOM.createRoot(root).render(<App />) and it manipulates the DOM for us. The DOM operations like, el.innerHTML = someText, or el.className = someClassName are abstracted away. This is why we like React--we get to write the simpler code: inputs and outputs, no side effects.

But eventually an app needs to do more than render elements, and so we actually do need to manage some side-effects like:

  • changing the scroll position
  • listening to window resize
  • managing focus
  • saving state to local storage
  • synchronizing state to the web audio API

Generally speaking, using an effect in React means to synchronize your app state with anything besides the DOM elements React is managing for you.

Reconciling the Element Tree

Let's back up and think about how React synchronizes your app state to the DOM, and how it reconciles the element tree. Consider this little app: as we edit text, the heading's text changes:

function App() {
const [name, setName] = useState('Ryan')
return (
<div>
<h1>{name}</h1>
<input
onChange={(event) => {
setName(event.target.value)
}}
/>
</div>
)
}

When we hand this app off to ReactDOM.render it's going to manage the side effects for us. Everytime we call setName it will call App(), compare the old return value (an element tree) to the new one, find the differences, and then call some DOM operations (side effects!) to synchronize your app state to the browser. In this case, it'll probably do something like heading.innerHTML = name when it needs to.

Let's Get Weird

Alright, how about we take over some of the rendering job ourselves? This is silly to do, but we're just trying to understand useEffect a bit more.

function App() {
let [name, setName] = useState('Ryan')
let headingRef = useRef()
// we'll update the heading ourselves
useEffect(() => {
let node = headingRef.current
node.innerHTML = name
})
return (
<div>
<h1 ref={headingRef} />
<input
onChange={(event) => {
setName(event.target.value)
}}
/>
</div>
)
}

With useEffect, we've told React to run a side effect every time App is rendered. In the end, nothing has changed about the app from the user's perspective, but as the developer we've taken over the DOM manipulation of the heading.

Looking at this code, you can kind of think about ReactDOM.render() as a useEffect hook like, I dunno, useReactDOM(<App/>, root)!

Diffing the "useEffect Tree"

There's one big difference though between these two apps: we manipulate the DOM every render but React only manipulates the DOM when the text is different. (Actually, there's another significant difference, we have a security concern because we used innerHTML without escaping, whereas React is safe by default).

For example, if we call setName("Brad") and then setName("Brad") again, our effect will still perform the DOM operation. Conversely, React would diff the element tree, notice nothing changed, and leave the heading alone on the second call to setName("Brad").

That's where the second argument to useEffect comes into play. It's a way for us to tell React not to perform the side effect unless the values in the array have changed. It's a way for useEffect to act like the element tree and participate in the "diff" part of React's synchornization of state to the user interface.

useEffect(
() => {
let node = headingRef.current
node.innerHTML = name
},
// React will diff the old `name` and
// the new `name` to decide if this
// effect should be run again
[name]
)

Now when we setName() and React calls App() again, not only will it diff the element tree to decide which of its own side effects need to be run, it will also diff our "effect tree" (the second argument to useEffect) to decide which of our side effects need to be run.

And it's awesome.

Please note that "effect tree" is not a real React term, its just an analogy in this article.

A Real Use Case

Probably the simplest real use case to understand is updating the document title:

function App() {
const [name, setName] = useState('Ryan')
useEffect(() => {
document.title = name
}, [name])
// ...
}

It's the same ol' React you already know and love, except now with hooks, its smart enough to diff your side effects the same way it diffs your elements--which previously was a lot harder to do on my own.

We're going to be digging into all the hooks in our new workshop this spring, we'd love to see you there :D


Photo by Jørgen Håland on Unsplash

Subscribe for updates on public workshops and blog posts

Don't worry, we don't send emails too often.

i love react
© 2024 ReactTraining.com