The Startup

Get smarter at building your thing. Follow to join The Startup’s +8 million monthly readers & +772K…

I Used React Context to Enable Dark Mode

Sanjar Kairosh
The Startup
Published in
5 min readOct 31, 2020

--

Many websites I browse offer the dark mode option, and sooner than later I switch to it. Both my WhatsApp and Notion are in dark mode.

Create React App Documentation in Dark Mode

I decided to make sure my personal website offered a darker theme as well.

My Website in Dark Mode

The theme of the website was controlled by the click of a button as you can see in the picture above.

Approach

The idea was to keep the theme in the state of some parent React component, for instance the App component. A function to toggle this state defined as toggleDark was passed down the tree to the button component.

The theme state variable was also passed down the tree, and every time the theme state toggled, components re-rendered with a new theme style.

However, I had to pass the theme state as a prop through multiple levels of the React tree. This meant many intermediary components did not utilize the theme state prop at all. It seemed wasteful. I pondered what would have happened had I implemented this with a larger React component tree.

Thus I wanted only the components in need of the theme state to have access to it. I decided to use React Context.

I could have implemented the Redux Store as an alternative, but my application was not that sizable that it needed a whole store to maintain the state of the application. React Context seemed like the perfect solution for the size and complexity of my application.

React Context

First I had to create the Context object. It was initialized with a default state.

const defaultState = {
dark: false,
toggleDark: () => {}
}
const ThemeContext = React.createContext(defaultState);

For every such React context object, there is context provider component.

<ThemeContext.Provider value={{dark, toggleDark}}>
{children}
</ThemeContext.Provider>

The value of this context provider is available to all the children components that consume the context, and every time this value updates, the consuming components re-render.

How do the children components consume the context? In the case of functional components, they subscribe to the context via the useContext hook. Every child component of the ThemeContext.Provider that subscribes to the context receives its context state from the value prop.

For example the Button component I wrote consumed the context as follows:

const Button = () => {
const contextState = React.useContext(ThemeContext);
return(
// Jsx Here
)
}

When react rendered this component it read the current context state from the closest matching Provider parent of ThemeContext . If there were no matching parent Providers the default context value was set.

Codepen Example

I didn’t want to share all of the code from my website in this article, so I replicated the same logic in a smaller example on Codepen.

I created a default context state, and the context object as the first step.

const defaultState = {
dark: false,
toggleDark: () => {},
}
const ThemeContext = React.createContext(defaultState);

I then had to create a custom ThemeProvider component.

const ThemeProvider = ({children}) => {
const [dark, setDark] = React.useState(false);

const toggleDark = (e, dark2) => {

let dark = !dark2
setDark(dark)

}


return (
<ThemeContext.Provider value={{dark, toggleDark}}>
{children}
</ThemeContext.Provider>
)
}

This was the context theme provider, however I added the dark state to it to keep a reference to the theme. I also defined the function that would toggle the theme by invoking setDark . I provided dark and toggleDark to the children that were to consume the context in the value prop of ThemeContext.provider .

I then included this custom context provider in the main parent App component.

const App = () => {

return(
<div className="app">
<div className="app-center">
<ThemeProvider>
<Navbar>
<Button/>
</Navbar>
<Content/>
</ThemeProvider>
</div>
</div>
)

}

Navbar , Button and Content components all subscribed to the context using the useContext hook.

const Button = () => {
const {dark, toggleDark} = React.useContext(ThemeContext);
return (
<button className="button" onClick={e => toggleDark(e,dark)}>
Toggle Theme
</button>
)
}
const Navbar = () => {
const {dark} = React.useContext(ThemeContext);
return(
<nav className={dark ? "navbar-dark" : "navbar"}>
{children}
</nav>
)
}
const Content = () => {
const {dark} = React.useContext(ThemeContext);
return(
<div className={dark ? "content-dark" : "content"}>
<h1>Content</h1>
<h4>Will Consume React Context</h4>
<p>Once the toggle theme button is pressed, the theme value in the React Context object will change, and accordingly this content will change its theme</p>
</div>
)
}

The button needed access to the toggle function to toggle the theme of the application, while the navigation bar and the content components only needed to subscribe to the darkstate and render the corresponding css style.

As you can see no props were passed down from the component that held the theme state to the components that needed the theme state. When a component required access to the theme state, it simply subscribed to the context and got access to the theme state.

I realize that for my example on Codepen you might argue why I even bothered to use React Context if there were only 2 or 3 levels of components. But I just wanted to share the logic and implementation of React Context in as simple a fashion as I could. My implementation of React Context for my personal website was more justified, as I had many more components throughout the React tree, that independently required access to the theme state, while many intermediary components were not even aware of the state.

Please let me know if I made any mistakes and if there are simpler ways to implement React Context or maybe not use React Context at all and have a simpler solution in place. Thank you for reading this article!

--

--

The Startup
The Startup

Published in The Startup

Get smarter at building your thing. Follow to join The Startup’s +8 million monthly readers & +772K followers.

Sanjar Kairosh
Sanjar Kairosh

Written by Sanjar Kairosh

Full Stack Engineer. Enjoys reading. Writes about a mixture of topics to satisfy curiosity.