React hooks in functional component
November 03, 2018
React’s useEffect
hook in a functional component can be executed at lifecyles of class component’s componentDidMount
, componentDidUpdate
, and componentWillUnmount
lifecycles.
By default, useEffect
gets triggered when an update happens to the React component.
This includes when a component receives new props from it’s parent component or even when you change the state locally. Let’s see example below.
import React, { useState, useEffect } from "react"
import ReactDOM from "react-dom"
const rootElement = document.getElementById("root")
const App = () => {
const [count, setCount] = useState(0)
useEffect(() => {
setTimeout(() => {
setCount(count + 1)
}, 1000)
})
return <h1>Count: {count}</h1>
}
ReactDOM.render(<App />, rootElement)
In this case, count
keeps increasing in every 1 second
because the component’s mounting triggers useEffect
, which updates the state change of count
in one second
and any state change triggers useEffect
again and that updates state again and this cycle keeps going on.
That means useEffect
hook gets executed at componentDidUpdate
as well as componentDidMount
.
If you want useEffect
not triggered at componentDidUpdate
for the state change of count
, exclude it in the second parameter of useEffect
, like so:
import React, { useState, useEffect } from "react"
import ReactDOM from "react-dom"
const rootElement = document.getElementById("root")
const App = () => {
const [count, setCount] = useState(0)
useEffect(() => {
setTimeout(() => {
setCount(count + 1)
}, 1000)
}, [])
return <h1>Count: {count}</h1>
}
ReactDOM.render(<App />, rootElement)
Here, empty array [] is second parameter of useEffect
and count
is excluded. Empty array means that nothing triggers execution of useEffect
. Therefore, useEffect
gets executed only at componentDidMount
lifecycle.
If you want useEffect
triggered at change of only count
value, provide [count]
as the second parameter.
If you want something like clear operation executed at componentWillUnmount
lifecycle, return a clear function in your useEffect
function, like so:
import React, { useState, useEffect } from "react"
import ReactDOM from "react-dom"
const rootElement = document.getElementById("root")
const App = () => {
const [count, setCount] = useState(0)
useEffect(() => {
setTimeout(() => {
setCount(count + 1)
}, 1000)
return () => console.log("clear something before component unmount.")
}, [])
return <h1>Count: {count}</h1>
}
ReactDOM.render(<App />, rootElement)
In this case, the function () => console.log("clear something before component unmount.")
will get executed before component unmounting.
useReducer hook
userReducer
hook takes 2 parameters. First one is a function as form of:
(oldState, action) => newState
and second parameter is initial value of the state of the function.
The example code below might self explain.
function Counter() {
const [count, dispatch] = useReducer((state, action) => {
switch (action.type) {
case "add":
return state + 1
case "subtract":
return state - 1
default:
return state
}
}, 0)
return (
<div>
<h1>Counter {count}</h1>
<button onClick={() => dispatch({ type: "add" })}>Add</button>
<button onClick={() => dispatch({ type: "subtract" })}>Subtract</button>
</div>
)
}
useLayoutEffect
useLayoutEffect
is identical to useEffect
, but it’s major key difference is that it gets triggered synchronously after all DOM mutation.
You only want to use this hook when you need to do any DOM changes directly, usually with useRef
.
This hook is optimized to allow the engineer to make changes to a DOM node directly before the browser has a chance to paint.
custom hook
Simple todo app with custom useArray
hook example.
import { useCallback, useMemo, useState } from "react"
export function useArray(initial) {
const [value, setValue] = useState(initial)
const add = useCallback(a => setValue(v => [...v, a]), [])
const move = useCallback(
(from, to) =>
setValue(it => {
const copy = it.slice()
copy.splice(to < 0 ? copy.length + to : to, 0, copy.splice(from, 1)[0])
return copy
}),
[]
)
const clear = useCallback(() => setValue(() => []), [])
const removeById = useCallback(
// work only when value object with id field.
id => setValue(arr => arr.filter(v => v && v.id !== id)),
[]
)
const removeIndex = useCallback(
index =>
setValue(v => {
const copy = v.slice()
copy.splice(index, 1)
return copy
}),
[]
)
return {
value,
setValue,
add,
move,
clear,
removeById,
removeIndex,
}
}
const App = () => {
const todos = useArray(["hi there", "sup", "world"])
return (
<div style={{ padding: 20 }}>
<h3>Todos</h3>
<button onClick={() => todos.add(Math.random())}>add</button>
<ul>
{todos.value.map((todo, i) => (
<li key={i}>
{todo}
<button type="button" onClick={() => todos.removeIndex(i)}>
delete
</button>
</li>
))}
</ul>
<button type="button" onClick={todos.clear}>
clear todos
</button>
</div>
)
}
export default App
Typescript version of the original example can be found here
Written by Sangche. Github