Context API in Next.js 13 with Best Practices and Clean Code

I am a highly qualified software developer with over 6 years of experience in developing desktop and web applications using various technologies.My main programming language is JavaScript, and I am currently working as a Front-end developer at Heybooster. I am passionate about software development and constantly seek to improve my skills and knowledge through research and self-teaching. I share my projects on Github as open source to not only learn and grow, but also contribute to the development community. As a software developer, I am committed to upholding ethical standards in my work.
Next.js 13 has brought in several new features that enhance the developer experience. In this article, we'll delve into how to effectively integrate the Context API in a Next.js 13 app, focusing on a real-world example: a Dark Mode Toggle.
Understanding the Context API in Next.js 13
Context provides a way to pass data through the component tree without having to pass props down manually at every level.
Setting up the Dark Mode Context
Our goal is to have a global toggle that switches the theme of our app between dark and light modes.
1. Define the Context:
In ThemeContext.ts, we define and export the context. We also set up a default context which is beneficial for type safety and understanding the shape of our context:
import { Dispatch, SetStateAction, createContext } from "react";
export interface ContextType {
darkMode: boolean;
setDarkMode: Dispatch<SetStateAction<boolean>>;
}
const defaultContext: ContextType = {
darkMode: false,
setDarkMode: (status: SetStateAction<boolean>): void => {},
};
export default createContext<ContextType>(defaultContext);
2. Create a Provider Component:
The ThemeProvider.tsx serves as the component wrapping parts of our app where we want the theme data accessible:
"use client";
import { ReactNode, useState } from "react";
import ThemeContext from "./ThemeContext";
export const ThemeProvider = ({ children }: { children: ReactNode }) => {
const [darkMode, setDarkMode] = useState<boolean>(false);
return (
<ThemeContext.Provider value={{ darkMode, setDarkMode }}>
{children}
</ThemeContext.Provider>
);
};
3. Accessing the Context:
For better abstraction and error handling, we create a custom hook named useTheme.ts. This hook abstracts the context retrieval and provides a clear error message if used outside the provider:
import { useContext } from "react";
import ThemeContext from "./ThemeContext";
export default function useTheme() {
const context = useContext(ThemeContext);
if (!context) {
throw new Error("useTheme must be used within a ThemeProvider");
}
return context;
}
Implementing the Dark Mode Toggle
Header Component: The Header.tsx component contains a button to toggle between the dark and light modes:
"use client";
import useTheme from "@/contexts/theme/useTheme";
const Header = () => {
const { darkMode, setDarkMode } = useTheme();
const className = darkMode ? 'bg-black text-white' : 'bg-white text black'
return (
<header className={`${className} h-20 flex items-center px-5 border-b`}>
<button onClick={() => setDarkMode(!darkMode)}>Toggle Dark Mode</button>
</header>
);
};
export default Header;
Card Component: The Card.tsx component adjusts its appearance based on the current theme:
"use client";
import useTheme from "./contexts/theme/useTheme";
interface Props {
title: string;
description: string;
}
const Card = ({ title, description }: Props) => {
const { darkMode } = useTheme();
const className = darkMode ? 'bg-black text-white' : 'bg-white text black'
return (
<div className={`p-6 rounded shadow-lg rounded-lg ${className}`}>
<h2 className="text-xl font-bold mb-2">{title}</h2>
<p>{description}</p>
</div>
);
};
export default Card
Best Practices:
Separation of Concerns: Separate the context creation, provider, and custom hooks into different files for better readability and maintenance.
Use Custom Hooks for Abstraction: Abstract the context retrieval logic into a custom hook (like
useTheme). This makes the context easier to use and provides better error handling.Type Safety with TypeScript: Define the shape of your context using TypeScript. This makes your code more robust and less error-prone.
Conclusion
By integrating the Context API with Next.js 13, we can efficiently handle global states like themes. Using best practices ensures that your app remains scalable, maintainable, and less error-prone. The Dark Mode example provides a practical implementation, showcasing the power of combining Next.js 13 features with the React Context API.
You can review the full code from here: https://github.com/mustafadalga/nextjs-playground/tree/context-api