useEffect.dev
← Back to lessons

Lesson 4. What are hooks anyway? 3. How React renders a component with effects

Instead of rendering a greeting message with the entered name, we want to call a web service each time the name is updated, which will return us an ID associated with the user name, stored in some database.

const WithLocalStateAndEffect = () => {
const [name, setName] = useState('John')
const [id, setId] = useState(0)
useEffect(() => {
getUserId(name).then((id) => setId(id))
}, [name])
return (
<>
<input value={name} onChange={(event) => setName(event.target.value)} />
<p>ID: {id}</p>
</>
)
}

Same as useState, useEffect will reserve some space in the memory (our HOOKS array), but not to store a state. What useEffect needs to store is the dependencies array so that it knows next time if the function must be executed again or not.

// Rendering 1
const [name, setName] = useState('John')
// → HOOKS[0] := [state: 'John']
const [id, setId] = useState(0)
// → HOOKS[1] := [state: 0]
useEffect(..., [name])
// → Executes the function
// → HOOKS[2] := [effect: ['John']]
return <> ...ID: 0... </>

On the first rendering, two spaces in the memory are initialized for the two local states, and a third for the useEffect, containing the dependencies, ['John'].

The second rendering is triggered when the promise inside useEffect is resolved, invoking setId, updating the component's state.

// setId(123) (when the promise in useEffect is resolved)
// → HOOKS[1] := [state: 123]
// ↓
// Rendering 2
const [name, setName] = useState('John')
// → HOOKS[0] already exists, returns 'John'
const [id, setId] = useState(0)
// → HOOKS[1] already exists, returns 123
useEffect(..., [name])
// → Dependencies ['John'] is already equal to HOOKS[2], do nothing
return <> ...ID: 123... </>

Although the state is modified, the dependencies array of useEffect is still evaluated to ['John'] (because name wasn’t modified), so the function is not executed again. Now, if we update the name in the input:

// setName('Jane') (when the input value is modified)
// → HOOKS[0] := [state: 'Jane']
// ↓
// Rendering 3
const [name, setName] = useState('John')
// → HOOKS[0] already exists, returns 'Jane'
const [id, setId] = useState(0)
// → HOOKS[1] already exists, returns 123
useEffect(..., [name])
// → Dependencies ['Jane'] is different from ['John']
// → Executes the function
// → HOOKS[2] := [effect: ['Jane']]
return <> ...ID: 123... </>

This time, name changed, so the function is useEffect is executed again, creating a new promise, which when resolved will trigger a new call to setId, therefore a new rendering:

// setId(456) (when the promise in useEffect is resolved)
// → HOOKS[1] := [state: 456]
// ↓
// Rendering 4
const [name, setName] = useState('John')
// → HOOKS[0] already exists, returns 'Jane'
const [id, setId] = useState(0)
// → HOOKS[1] already exists, returns 456
useEffect(..., [name])
// → Dependencies ['Jane'] is already equal to HOOKS[2], do nothing
return <> ...ID: 456... </>

The model described here is more straightforward than the real one but is good enough to understand how hooks work under the hood. Plus, since all the hooks could be written using useState and useEffect, it allows you to imagine what happens with all the other hooks.