E-CommerceCompetitive PricingMarketing Ready
Dotcreo Digital logo

How To Setup Global useContext In Next.js

Next.js App Router
15 mins read
19th Feb 2419th Feb 24

Introduction

First things first, deciding whether to use the 'useContext' hook from React over a third party library like Redux or Zustand should solely depend on the complexity of your application and its use cases for a global state management system. In most cases, using 'useContext' for a few binary switches pertaining to modals and mobile navigations is just fine and the aforementioned are redundant. It's lightweight and built into React, making it easy to use for basic scenarios.

However as your application grows, and you need to mutate state with a variety of data types such as objects and arrays, the redundancy for said third party management library reverses. React's inbuilt 'useContext' hook primarily deals with prop drilling and sharing simple values. If your global state involves complex data types like objects or arrays and requires mutations, a dedicated state management library can offer better support for handling these scenarios. Libraries like Redux provide a clear and structured way to manage such complex state.

If you think your state management requirements will be rather complex, see my other article How To Setup Redux Toolkit In Nextjs.

How To Read This Guide

This article assumes you already have an existing Next.js application based on the App Router architecture.

If you want to follow along, you can just create a basic application with npx create-next-app@latest. Remember to choose the App Router option, none of the other default options are necessary for this to work.

Getting Started

I will first explain the steps, then we will break each one down. Here they are:

  1. Create a Global State Provider file.
  2. Import necessary hooks from React.
  3. Create and export a custom hook that consumes your global state and provides it to its children.
  4. Import the Global State Provider file to use with your application.
  5. Use your global state.

Step One: Create a Global State Provider

Okay, great. So first let's create a providers.jsx file as per the first step. This should be a file marked 'use client', you can place it in the same folder as your layout.jsx file or, by convention, in a folder called context. It will hold our global state and provide it to the rest of the app.

This is the basic shell of what will be our custom hook that will consume the context we create in the next step.

'use client'

function Providers({ children }) {

  return (
    <AppContext>
      {children}
    </AppContext>
  )
}

module.exports = {
  Providers
}

Step Two: Import Necessary Hooks from React

For the second step, we need to import our required hooks, initialize our application context, initialize the state we want to change, and make a few other adjustments. It should look something like this:

'use client'

import { createContext, useContext, useState } from 'react'

const AppContext = createContext()

function Providers({ children }) {
  const [isBookingModalOpen, setIsBookingModalOpen] = useState(false)

  return (
    <AppContext.Provider value={{ isBookingModalOpen, setIsBookingModalOpen }}>
      {children}
    </AppContext.Provider>
  )
}

module.exports = {
  Providers
}

You can see here I've done four things:

  1. Import the necessary hooks from react.
  2. Initialize your context with const AppContext = createContext(). You can name the context as per your preference.
  3. In this instance, we're using the example of toggling the visibility of a booking modal, so initialize that state here: const [isBookingModalOpen, setIsBookingModalOpen] = useState(false).
  4. Modify the AppContext component to pass the state value and setter function to the Provider component whilst also adding .Provider to the opening and closing AppContext component brackets.

Step Three: Create & Export a Custom Hook

For the third step, we need to add another named export function. This will be responsible for utilizing the function we created in the previous step, and providing the state to its children. Your finished providers.jsx file should look something like this:

'use client'

import { createContext, useContext, useState } from 'react'

const AppContext = createContext()

function Providers({ children }) {
  const [isBookingModalOpen, setIsBookingModalOpen] = useState(false)

  return (
    <AppContext.Provider value={{ isBookingModalOpen, setIsBookingModalOpen }}>
      {children}
    </AppContext.Provider>
  )
}

function useAppContext() {
  return useContext(AppContext)
}

module.exports = {
  Providers,
  useAppContext
}

Step Four: Import Global State Provider

For the last setup step, we need to import our Providers function into our Layout.jsx from our Providers.jsx file and wrap the RootLayout component's children with the Providers component. This will make our global state available to all of its children. Once you've done that, it should look something like this:

import { Inter } from 'next/font/google'
import './globals.css'

import { Providers } from '@/app/providers'

const inter = Inter({ subsets: ['latin'] })

export const metadata = {
  title: 'Create Next App',
  description: 'Generated by create next app',
};

export default function RootLayout({ children }) {
  return (
    <html lang='en'>
      <body className={inter.className}>
        <Providers>
          {children}
        </Providers>
      </body>
    </html>
  )
}

Step Five: Using The Global State

Great! If you've followed step-by-step so far, you should have everything setup accordingly ready to be used by your client components. Carrying on from the example of needing to toggle the visiblity of a modal, let's create another custom client component which is just a button. You can of course import the useAppContext function into a larger client component, but for the sake of best practises I will encourage that people narrow down the use of the use client directive to the components that need it. Server rending as much of your application as possible will ensure faster loading times for your end user.

Here's an example of a simple button component called OpenBookingModalButton.jsx that is composable and able to imported anywhere in my server rendered pages:

'use client'

import { useAppContext } from '@/app/providers'

export default function OpenBookingModalButton({ text, className }) {
  const { setIsBookingModalOpen } = useAppContext()

  return (
    <button 
      className={className}
      onClick={() => setIsBookingModalOpen(true)}
    >
      {text}
    </button>
  )
}

and then in the BookingModal.jsx file:

'use client'

import { useAppContext } from '@/app/providers'

export default function BookingModal() {
  const { isBookingModalOpen, setIsBookingModalOpen } = useAppContext()

  return (
    <>
      {isBookingModalOpen && (
        <div className='booking-modal'>
          <div className='booking-modal__header>
            <button onClick={() => setIsBookingModalOpen(false)}>
              Close
            </button>
          </div>
          <div className='booking-modal__content'>
            // BookingModal content code
          </div>
        </div>
      )}
    </>
  )
}

Extending Your Global State

Remember, if you wanted to expand your global state variables in your context, ensure that you consider whether opting for a third-party state management library is the best option. If not, then you can do something like this in your Providers.jsx file:

'use client'

import { createContext, useContext, useState } from 'react'

const AppContext = createContext()

function Providers({ children }) {
  const [isBookingModalOpen, setIsBookingModalOpen] = useState(false)
  const [anotherState, setAnotherState] = useState(null)
  const [yetAnotherState, setYetAnotherState] = useState(null)

  return (
    <AppContext.Provider value={{ 
      isBookingModalOpen, 
      setIsBookingModalOpen, 
      anotherState, 
      setAnotherState,
      yetAnotherState,
      setYetAnotherState
    }}>
      {children}
    </AppContext.Provider>
  )
}

function useAppContext() {
  return useContext(AppContext)
}

module.exports = {
  Providers,
  useAppContext
}
Samuel King headshot
Author: Samuel King
Full Stack DeveloperDesignerMarketer
Book A Free Call →