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) => newStateand 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 AppTypescript version of the original example can be found here
Written by Sangche. Github