How to Rerender Component on Resize in React
When working with complex UIs in React, such as a masonry layout, you may need to adjust the display and position of elements based on the available screen size. In such cases, we need to re-render components when the screen is resized.
In this tutorial, we will take a look at how you can re-render components when the screen is resized and how you can create a custom hook to reuse this functionality elsewhere in your application.
Rerender on Resize with useEffect
To re-render a component on screen resize, we need to introduce a useEffect
hook that attaches a resize event listener to the document:
import React, { useState, useEffect } from 'react'
const App = () => {
const [size, setSize] = useState({
width: window.innerWidth,
height: window.innerHeight
})
useEffect(() => {
const resize = () => {
setSize({
width: window.innerWidth,
height: window.innerHeight
})
}
window.addEventListener('resize', resize)
}, [])
return (
<h1>Your screen size is: {size.width}x{size.height}</h1>
)
}
export default App
The above example works in the following way:
- Line 4: We create a new state using the
useState
hook. - Line 9: We add a
useEffect
hook that will be triggered when the component is mounted to the DOM. The reason this is triggered on mount is that the hook has an empty dependency array as the second parameter. - Line 10: We create a
resize
function that calls thesetSize
updater function with the updated values for the screen size. - Line 17: We attach this function to the
resize
event usingwindow.addEventListener
.
The dependency array tells React when the useEffect
should run. An empty array means it has no dependencies, so it will only run once when the component is mounted.
Cleaning up the resize on unmount
This works perfectly; however, there are some improvements that we can make. Currently, the event listener remains attached to the DOM even if the component is unmounted. This can cause performance issues and potentially bugs.
To resolve this, return a function from the useEffect
hook that will be called when the component is unmounted:
useEffect(() => {
const resize = () => {
setSize({
width: window.innerWidth,
height: window.innerHeight
})
}
window.addEventListener('resize', resize)
return () => {
window.removeEventListener('resize', resize)
}
}, [])
Adding Debounce to Improve Performance
This solution, however, has a big downside. It affects performance as the resize
event is being called on each pixel change. This results in a lot of unnecessary calls and redraws.
We can optimize this by introducing a debounce function. This ensures that the resize
function is only called once every x milliseconds. Add the following function to your application:
const useDebounce = (func, milliseconds) => {
const time = milliseconds || 400
let timer
return event => {
if (timer) {
clearTimeout(timer)
}
timer = setTimeout(func, time, event)
}
}
export default useDebounce
This function works by delaying the execution of the passed function using a setTimeout
, with a default delay of 400 milliseconds. Notice that we can pass the original event through by using the third parameter of the setTimeout
function. To use it, wrap the resize
function with the useDebounce
function in the following way:
// Don't forget to import useDebounce at the top of the file
import useDebounce from './useDebounce'
useEffect(() => {
const resize = useDebounce(() => {
setSize({
width: window.innerWidth,
height: window.innerHeight
})
})
window.addEventListener('resize', resize)
return () => {
window.removeEventListener('resize', resize)
}
}, [])
Now, whenever we resize the window, the function will only be triggered if there are no new events for 400 milliseconds. This greatly reduces the number of function calls.
Creating a Custom useResize Hook
To make this functionality reusable, we can take this code one step further and create a custom React hook. Extract the useState
and useEffect
hooks into a separate function that returns the value from the useState
hook:
import { useState, useEffect } from 'react'
import useDebounce from './useDebounce'
const useResize = () => {
const [size, setSize] = useState([window.innerWidth, window.innerHeight])
useEffect(() => {
const resize = useDebounce(() => {
setSize([
window.innerWidth,
window.innerHeight
])
})
window.addEventListener('resize', resize)
return () => {
window.removeEventListener('resize', resize)
}
}, [])
return size
}
export default useResize
Unlike the previous example, this hook uses an array for the state. The reason for this is that we can destructure both width and height into variables when we need to use this hook inside a component.
Make sure to return the size
state variable at the end of the useResize
hook.
To use the hook in a component, simply import it and destructure the returned array into width
and height
variables:
import useResize from './useResize'
const App = () => {
const [width, height] = useResize()
return (
<h1>Your screen size is: {width}x{height}</h1>
)
}
Summary
In summary, when working with resize event listeners, make sure you always debounce them to prevent any performance bottlenecks. Also, don't forget to remove the event listener during the unmount phase to clean up the application from unnecessary event listeners.
If you are interested in learning more about React, be sure to check out our guided roadmap below. Do you have any questions that this tutorial did not cover? Let us know in the comments below! Thank you for reading, Happy coding! π¨βπ»
Rocket Launch Your Career
Speed up your learning progress with our mentorship program. Join as a mentee to unlock the full potential of Webtips and get a personalized learning experience by experts to master the following frontend technologies: