Responsive Background Images Using React Hooks🍍

Siri Lööf
ITNEXT
Published in
5 min readFeb 22, 2019

--

Getting to know the new React Effect- and State Hooks in a real-life example.

Working with full-screen background images can be difficult. I often find that very few full-screen images look good in both landscape and portrait orientation, so I like to use different images depending on the viewport width. Usually I would let media queries in CSS handle the responsiveness of the site, but sometimes image URLs are only available in JavaScript, for example if loaded in via an Ajax request.

Faced with these issues in a React application, I decided to try out the new Hooks API to solve them. With React Hooks, we can both subscribe to changes in viewport size and set the appropriate background image based on the width of the screen! 🎉

In my React app, I’ve chosen one background image adapted for desktop screens and another suitable for mobile, as per this design:

Narrow screens use an image adapted for portrait orientation and wider screens use the landscape image

Below is my React component. Note that this is a functional component, since Hooks do not work with class components. Screens narrower than 650px will use the image adapted for mobile. Wider screens will use the desktop image.

I also have some basic CSS to get the background image to be full-screen and to position the text:

Now this works fine even without Hooks when the component first renders, but if I start re-sizing the window, even below the 650px breakpoint, the image url doesn’t change. To get this desired side effect, we need to attach a listener to the window’s re-size event, by using the Effect Hook.

The Effect Hook

The Effect Hook, useEffect, lets us perform side effects in a React functional component. With this hook, I only have to add the event listener once and it runs every time the component renders. It’s basically the equivalent of a class component’s componentDidMount and componentDidUpdate lifecycle methods combined. Here’s what the effect looks like inside my App component:

useEffect(() => {        
window.addEventListener('resize', handleWindowResize);
});

I also want to remove the event listener when the component unmounts. In a class component, this would be done in the componentWillUnmount method. With the Effect Hook, we can keep all the code related to the side effect in one place — since the return value of useEffect acts as a cleanup method. Here’s what that looks like:

useEffect(() => {        
window.addEventListener('resize', handleWindowResize);
return () => {
window.removeEventListener('resize', handleWindowResize);
}
});

By default, the Effect Hook runs every time the component mounts and again every time it updates. But this is unnecessary in my case. To fix this, we will use the second argument to useEffect, which is an array of dependencies. Since I have no dependencies to my hook, I pass an empty array as the second argument, and this makes the side effect run only when the component mounts and unmounts, which is what we want:

useEffect(() => {        
window.addEventListener('resize', handleWindowResize);
return () => {
window.removeEventListener('resize', handleWindowResize);
}
}, []); //empty array makes side effect only run on mount and unmount

The State Hook

In my handleWindowResize function, I now want to update the component’s state with the new value of window.innerWidth. But hang on, this is a functional component, so how can I change its state without refactoring to a class component? The answer is: with the State Hook.

The State Hook lets us use component state even in functional components. The way to declare a state variable with useState looks like this:

const [windowWidth, setWindowWidth ] = useState(window.innerWidth);

The first item in the array, windowWidth, is the equivalent to this.state.windowWidth in a class component. The second item, setWindowWidth, is a setter function, equivalent to setState({ windowWidth: window.innerWidth }) in a class component. The only argument to useState is the initial value of that particular state variable, in my case the initial width of the window.

Finally, this is what the handleWindowResize function looks like, and I declare it inside my useEffect hook. It calls the setter function for updating the windowWidth state:

useEffect(() => {
const handleWindowResize = () => {
setWindowWidth(window.innerWidth);
}
window.addEventListener('resize', handleWindowResize); return () => {
window.removeEventListener('resize', handleWindowResize);
}
}, []);

Edit: In the code section above, it looks like setWindowWidth is a dependency to the useEffect hook, since it’s declared outside the hook. While it is true that it is technically a dependency and we could specify it in our dependency array, we don’t actually have to because values from useState is guaranteed to be static by React.

My React component now looks like this with both the State Hook and the Effect Hook in use:

Now the state variable windowWidth is updated every time the window is re-sized! 🎉

Custom Hooks

There might be other components in the application that need to know about the window width too. To avoid having to write the same code in multiple components, we can extract the code handling the side effect and the stateful logic and create a custom hook. A custom hook is just a JavaScript function that calls other React Hooks. The name of the function should start with ‘use’, according to the React documentation. I decided to call my custom hook useWindowWidth and here it is in action:

Verdict

When I first saw the documentation for React Hooks I wasn’t sure how useful they would be in my daily work, but now that I’ve started introducing them in projects, especially the Effect Hook has become very appealing. I like that all code related to one side effect is kept in the same place and not scattered throughout various lifecycle methods. There’s a lot less code to write than creating a class component with the same logic. The syntax is still a bit weird to me, but I guess it’s just a matter of preference and habit.

Hopefully, as hooks become more frequently used I will discover further benefits and use cases. If not, at least it’s good to know that we now have the choice to use functional- or class components when handling state and side effects in React.

--

--