Creating A Light/Dark Themed React App Using Context#

Last Updated: October 2, 2023

My project source code is available here

Overview#

React Context is a way to pass data through the component tree without having to pass props down manually at every level. In this app, I’ve created a basic light/dark theme switcher using React Context.

Files#

App.js#

Note

We can see the context provider is defined in the App component of App.js.

function Root() {
    return (
        <ThemeProvider>
            <App/>
        </ThemeProvider>
    );
}

We can avoid re-rendering of components in the context tree using memoization in React.

https://react.dev/reference/react/memo#memo

This is applied using the memo() function hook.

import {memo} from "react";

const App = memo(() => {
});
App.js
import "./App.css";
import {ThemeProvider, useTheme} from "./ThemeContext";
import Switch from "./Switch";
import {memo} from "react";

const Title = ({children}) => {
    const {theme, toggleTheme} = useTheme();
    return (
        <h2
            style={{
                color: theme === "light" ? "black" : "white",
            }}
        >
            {children}
        </h2>
    );
};

const Paragraph = ({children}) => {
    const {theme, toggleTheme} = useTheme();
    return (
        <p
            style={{
                color: theme === "light" ? "black" : "white",
            }}
        >
            {children}
        </p>
    );
};

const Content = () => {
    return (
        <div>
            <Paragraph>
                We are a pizza loving family. And for years, I searched and searched and
                searched for the perfect pizza dough recipe. I tried dozens, or more.
                And while some were good, none of them were that recipe that would make
                me stop trying all of the others.
            </Paragraph>
        </div>
    );
};

const Header = () => {
    return (
        <header>
            <Title>Little Lemon</Title>
            <Switch/>
        </header>
    );
};

const Page = () => {
    return (
        <div className="Page">
            <Title>When it comes to dough</Title>
            <Content/>
        </div>
    );
};

// using memoization: https://react.dev/reference/react/useMemo#skipping-re-rendering-of-components
const App = memo(() => {
    const {theme, toggleTheme} = useTheme();
    return (
        <div
            className="App"
            style={{
                backgroundColor: theme === "light" ? "white" : "black",
            }}
        >
            <Header/>
            <Page/>
        </div>
    );
});

function Root() {
    return (
        <ThemeProvider>
            <App/>
        </ThemeProvider>
    );
}

export default Root;

ThemeContext.js#

Note

The ThemeContext.js file contains the context provider and the context.

The context is defined using the createContext method.

const ThemeContext = createContext(undefined);

The context provider is defined as a component with the ‘{children}’ prop.

const ThemeProvider = ({children}) => {
}
ThemeContext.js
import {createContext, useContext, useState} from "react";

const ThemeContext = createContext(undefined);

const ThemeProvider = ({children}) => {
    // Give the child component a `theme` state
    const [theme, setTheme] = useState("light");

    // Return a `ThemeContext.Provider` component that receives an object
    // as its value prop, with a `theme` string and a `toggleTheme` function
    return (
        // https://react.dev/reference/react/createContext#provider
        // Wrap your components into a context provider to specify
        // the value of this context for all components inside:
        <ThemeContext.Provider
            value={{
                theme: theme,
                toggleTheme: () => setTheme(theme === "light" ? "dark" : "light"),
            }}
        >
            {children}
        </ThemeContext.Provider>
    );
};

// Change `useTheme` so that it returns `useContext(ThemeContext);`
// instead of the `{theme: "light"}` object
const useTheme = () => useContext(ThemeContext);

// export const ThemeProvider = ({ children }) => children;
// export const useTheme = () => ({ theme: "light" });
export {ThemeProvider, useTheme};

Switch/index.js#

The Switch component is a simple checkbox that toggles the theme when clicked.

Note

The Switch component is located in ./Switch directory with the filename index.js and Styles.css to bundle the component with the custom CSS.

./Switch/index.js
import "./Styles.css";
import {useTheme} from "../ThemeContext";

const Switch = () => {
    // Use the useTheme hook to get access to `theme` and `toggleTheme`
    const {theme, toggleTheme} = useTheme();

    // Add an `onChange` prop to the input element. When them element changes,
    // the `toggleTheme` function should be invoked
    // Change the `checked` prop to be true or false based on the `theme` state.
    return (
        <label className="switch">
            <input
                type="checkbox"
                checked={theme === "light"}
                onChange={toggleTheme}
            />
            <span className="slider round"/>
        </label>
    );
};

export default Switch;

index.js#

index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
    <React.StrictMode>
        <App/>
    </React.StrictMode>
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

References#