Write custom React hook to change background color of the page

In this article, we will be writing a simple custom React hook which will be based on React useEffect hook from react and allow us to control background page color. For example, to enable dark mode.

First, let's create a new React project

yarn create react-app dark-background
cd dark-background

Modify existing CSS

By default create-react-app project background is dark already so we need to disable it.

Navigate to the App.css and comment line with background-color of the .App-header CSS class.

/*  src/App.css */
.App {
  text-align: center;
}

.App-logo {
  height: 40vmin;
}

.App-header {
  /* background-color: #282c34; */
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  font-size: calc(10px + 2vmin);
  color: white;
}

.App-link {
  color: #09d3ac;
}

Do the yarn start and verify that the starting page loads with the white background color.

Create a simple custom hook which switches the color

I'm going to create a hooks folder under src with index.js and useDarkBody.js files.

// hooks/useDarkBody.js
import { useEffect } from 'react'

function useGreyBody() {
  useEffect(() => {
    document.body.style.backgroundColor = '#282c34'

    return () => {
      document.body.style.backgroundColor = '#fff'
    }
  })
}

export default useGreyBody

When used useGreyBody() will set a page background color to #282c34 otherwise color will be #fff.

Export our hook from the index file

In order to use our custom hook whatever we want from the hooks/ folder let's our hook as default export from hooks/index.js:

// hooks/index.js
export { default as useDarkBody } from './useDarkBody'

Use a custom hook in App.js

Navigate to the App.js and invoke useDarkBody() before render function:

// src/App.js
import React from 'react'
import logo from './logo.svg'
import './App.css'

import { useDarkBody } from './hooks'

function App() {
  useDarkBody()
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>
          Edit <code>src/App.js</code> and save to reload.
        </p>
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
          Learn React
        </a>
      </header>
    </div>
  )
}

export default App

yarn start do start the app an verify that color is now changed to dark. You can use this hook whenever need in other components as you wish.

Congrats! We've created a custom re-usable hook which does one simple thing :)

Bonus section: how to add tests for your custom hook

Nice thing is that you can create tests for your hooks independently. Under hooks/ let's create a test file useDarkBody.spec.js:

import React from 'react'
import { mount } from 'enzyme'

import useDarkBody from './useDarkBody'

describe('useDarkBody', () => {
  it('should set background body color to dark when invoked.', () => {
    expect(document.body.style.backgroundColor).toMatchSnapshot()

    const Component = () => {
      useDarkBody()

      return null
    }

    mount(<Component />)

    expect(document.body.style.backgroundColor).toMatchSnapshot()
  })

  it('should revert to white when the component is unmounted.', () => {
    const Component = () => {
      useDarkBody()

      return null
    }

    const component = mount(<Component />)

    expect(document.body.style.backgroundColor).toMatchSnapshot()

    component.unmount()

    expect(document.body.style.backgroundColor).toMatchSnapshot()
  })
})

In order to run the tests add Enzyme as a dev dependency to our project with its adapter and test-renderer by running:

yarn add -D enzyme enzyme-adapter-react-16 react-test-renderer

Configure Enzyme Adapter

Under src create a file called setupTests.js:

// src/setupTests.js
import { configure } from 'enzyme'
import Adapter from 'enzyme-adapter-react-16'

configure({ adapter: new Adapter() })

Run the tests with yarn test and all should be green :)