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 1const [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 2const [name, setName] = useState('John')// → HOOKS[0] already exists, returns 'John'const [id, setId] = useState(0)// → HOOKS[1] already exists, returns 123useEffect(..., [name])// → Dependencies ['John'] is already equal to HOOKS[2], do nothingreturn <> ...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 3const [name, setName] = useState('John')// → HOOKS[0] already exists, returns 'Jane'const [id, setId] = useState(0)// → HOOKS[1] already exists, returns 123useEffect(..., [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 4const [name, setName] = useState('John')// → HOOKS[0] already exists, returns 'Jane'const [id, setId] = useState(0)// → HOOKS[1] already exists, returns 456useEffect(..., [name])// → Dependencies ['Jane'] is already equal to HOOKS[2], do nothingreturn <> ...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.