Advanced React

Lifecycles

Each component in React has a lifecycle that you can monitor and manipulate during its three main phases.

The three phases are Mounting, Updating, and Unmounting.


Mounting

Mounting means putting the elements into the DOM

In React functional component, to let a function called in this phase, you can put them in useEffect hook with empty arrayDependencies. The effect will perform only once when the component is rendered and since the dependencies array is empty, it will not perform when there are any other changes in the component.

Some typical things you want to do in this phase are adding an event listener to a global element like window, or fetching data only one time from the server to be used in the component. Check the example below:

function Count() {
const [selectedUser, setSelectedUser] = useState(null)
const [users, setUsers] = useState([])
const fetchUsers = useCallback(async() => {
const result = await fetch('https://randomuser.me/api/?results=5').then(rs => rs.json())
const { results } = result || {};
setUsers(results)
}, [])
// fetch users list when component mounted
useEffect(() => {
fetchUsers()
}, [])
return (
<>
<select
defaultValue=""
onChange={(e) => setSelectedUser(e.target.value)}
>
<option disabled value="">Select a user</option>
{users.map(u => (
<option value={u.email} key={u.email}>
{u.name.first} {u.name.last}
</option>
))}
</select>
<div>
Selected user: {selectedUser}
</div>
</>
)
}

Updating

The next phase in the lifecycle is when a component is updated.

A component is updated whenever there is a change in the component's states or props.

In Basic React section, we already talked about useEffect, which will perform a side effect when there is a change in the arrayDependencies. This makes useEffect perfect for hooking events in this phase. Check the example below:

function Count() {
const [count, setCount] = useState(0)
useEffect(() => {
document.title = `Current count is ${count}`
}, [count])
return (
<>
<div>Current count is {count}</div>
<Button onClick={() => setCount(c => c + 1)}>+</Button>
<div>
Look at the title of the current tab to see the side effect
</div>
</>
)
}

For performance optimization, if you want to control whether the components should re-render or not when states change, you can use useReducer hook instead of useState. If you want the component to re-render, return a copy of the current state object, and if you just want to update the variables without re-rendering, return the current state object. Check the example below:

function Count() {
function reducer(state, action) {
switch(action.type) {
case 'increase':
state.count = state.count + 1
break
case 'decrease':
state.count = state.count - 1
break
default:
throw new Error()
}
// returning copy of state if rerender flag exists
if (action.rerender) {
return {...state}
}
return state
}
const [state, dispatch] = useReducer(reducer, { count: 0 })
return (
<>
<div>Current count is {state.count}</div>
<div>
<Button onClick={() => dispatch({ type: "increase" })}>+</Button>
<Button
onClick={() => dispatch({ type: "increase", rerender: true })}
>
+ (with rerender)
</Button>
</div>
<div>
<Button onClick={() => dispatch({ type: "decrease" })}>-</Button>
<Button
onClick={() => dispatch({ type: "decrease", rerender: true })}
>
- (with rerender)
</Button>
</div>
</>
)
}

Unmounting

The next phase in the lifecycle is when a component is removed from the DOM, or unmounting as React calls.

Usually, this phase is when we should clean up things to prevent memory leaks. Check the example below

function Count() {
const [count, setCount] = useState(0)
useEffect(() => {
const handleClickWindow = () => {
setCount(c => c + 1)
}
window.addEventListener("click", handleClickWindow)
/*
Cleaning the listener since React doesn't control the global element, so the listener is still there even when this component is unmounted
*/
return (() => {
window.removeEventListener("click", handleClickWindow)
})
}, [])
return (
<>
<div>Current count is {count}</div>
<div>Click everywhere in the current window to increase count</div>
</>
)
}
Previous
NextJS