Skip to main content

Building a Theme Switcher with Context (Part 2): Adding More Features #101

📖 Introduction

Following our creation of a basic theme switcher, this article explores how to make it more robust and user-friendly. We will add a key feature: persisting the theme to local storage. This will ensure that the user's theme preference is remembered across browser sessions.


📚 Prerequisites

Before we begin, please ensure you have a solid grasp of the following concepts:

  • All concepts from Part 1 of this series.
  • The localStorage API.

🎯 Article Outline: What You'll Master

In this article, you will learn:

  • The Problem: Understanding why a non-persistent theme can be a poor user experience.
  • Core Implementation: How to use the localStorage API to save and retrieve the user's theme preference.
  • Practical Application: Enhancing our theme switcher to persist the theme across browser sessions.

🧠 Section 1: The Core Concepts of a Persistent Theme Switcher

Our current theme switcher works well, but it has one major flaw: if the user refreshes the page or closes the browser, their theme preference is lost. This is because the theme is only stored in the component's state, which is reset on every page load.

To fix this, we can use the localStorage API. localStorage allows us to store key-value pairs in the browser that persist even after the browser is closed.

We will modify our ThemeProvider to:

  1. Initialize the theme state from localStorage when the component mounts.
  2. Save the theme to localStorage whenever it changes.

💻 Section 2: Deep Dive - Implementation and Walkthrough

Let's enhance our ThemeProvider.

2.1 - The Enhanced ThemeProvider

// ThemeProvider.js
import React, { useState, useEffect, createContext } from 'react';

export const ThemeContext = createContext();

export function ThemeProvider({ children }) {
const [theme, setTheme] = useState(() => {
const savedTheme = localStorage.getItem('theme');
return savedTheme || 'light';
});

useEffect(() => {
localStorage.setItem('theme', theme);
}, [theme]);

const toggleTheme = () => {
setTheme(prevTheme => (prevTheme === 'light' ? 'dark' : 'light'));
};

return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
}

Step-by-Step Code Breakdown:

  1. useState(() => { ... }): We've updated our useState call to use a function as the initial value. This function is only executed on the initial render.
    • localStorage.getItem('theme'): We try to get the theme from localStorage.
    • return savedTheme || 'light';: If a theme is found in localStorage, we use it. Otherwise, we default to 'light'.
  2. useEffect(() => { ... }, [theme]): We use a useEffect hook to save the theme to localStorage whenever the theme state changes.

The rest of our application (App.js, ThemeSwitcher.js, and App.css) can remain the same.


💡 Conclusion & Key Takeaways

In this article, we've made our theme switcher much more user-friendly by persisting the theme to local storage. This is a common pattern that you will use in many of your React applications.

Let's summarize the key takeaways:

  • The localStorage API is a great way to persist data across browser sessions.
  • You can use a function as the initial value of useState to lazily initialize the state.
  • The useEffect hook is perfect for synchronizing state with an external system like localStorage.

Challenge Yourself: To solidify your understanding, try to add a feature that allows the user to choose from a list of themes (e.g., "light", "dark", "sepia") and persist their choice to local storage.


➡️ Next Steps

You now have a solid understanding of how to build a robust and practical feature with the Context API. In the next article, "Combining Context with useReducer for Complex State (Part 1)", we will explore how to use the useReducer hook with the Context API to manage more complex state.

Thank you for your dedication. Stay curious, and happy coding!


glossary

  • localStorage: A web storage API that allows you to store key-value pairs in a web browser with no expiration date.
  • Lazy Initialization: A performance optimization where you delay the initialization of an object until the first time it is needed.

Further Reading