How to use Context API with SSR in Next.js app

Jan 07 20232 min read
How to use Context API with SSR in Next.js app

What is SSR?

SSR is a mechanism in NextJS in which we receive data from server on each request and pass it to the page via props. NextJS offers a method called getServerSideProps() at page level that gets executed on each request and returns props that are further passed on to the page component.

This data may be required by its child components at any level. So, we need a way to make it accessible to the child components.

Passing data to child components

In a typical react application data is passed from parent to child components via props. But if the child components are increasing over time, it becomes difficult to pass data to deeply nested components. There are 2 common ways to do this.

Prop Drilling

One way to do this is to pass props to the child component and child component will do the same until the last child receives the props. This is called prop drilling. This makes it difficult to maintain the application. An alternate to this approach is the context api.

Context Api

Another way is to use Context api of React. Context is used to store values that are global in application like details of an authenticated user, tags, and category of a blog.

These values are accessible to the components (and their child components) that are falling under this context. This way, it saves you from passing props at each level down and offers an effective way to access common data at each component level.

The idea is to create context component and put values in it. On the other hand, components that are falling under that context will have access to the values which are stored in that context.

To use context api, you need to follow below 3 steps.

Create context Component

You create a context component (with default values) using a React hook called createContext() to hold values. You can create as many contexts as you want. Here is the syntax.

const ToolContext = createContext({})
export default ToolContext;

A context has 2 parts called Provider and Consumer.

Provider

Provider is used to put values in the context. It is a component and takes in other components which you want to give access to. These components (along with their child components) will have access to the context values. Here is the syntax.

<UsernameContext.Provider value={{username}}>
            <Component2/>
</UsernameContext.Provider>

Consumer

Consumer is the component in which you mention other components that want to consume the context values. These components must be the same (or downline) as what you mentioned inside the Provider. Here is the syntax.

<UsernameContext.Consumer>
  {value => (
      <h2>{value.username}</h2>
  )}
</UsernameContext.Consumer>

The Consumer can also be written using a React hook called useContext().

Example

Let’s take example of an application where we have 4 components as below:

  1. HomePage
  2. Component2
  3. Component3 
  4. Component4

HomePage calls Component2, Component2 calls Component3 and Component3 calls Component4.

Suppose you are receiving the username of logged in user via the getServerSideProps() method of the HomePage, and you want to show it in the HomePage and Component4.

First, we will create a context component called UsernameContext that will be used to hold context values.

// src/context.js
import {createContext} from "react";

export const UsernameContext = createContext({})

This is the HomePage component in pages folder that will be called from browser.

// pages/HomePage.js
import {Component2} from "../src/components";
import React from "react";
import {UsernameContext} from "../src/context";

export default function HomePage({username}) {
  return <div style={{backgroundColor: "lightgrey", border: '1px solid silver', height: '100vh'}}>
    <h1>HomePage, {username}</h1>
    <UsernameContext.Provider value={{username}}>
      <Component2/>
    </UsernameContext.Provider>
  </div>
}

export async function getServerSideProps() {
  const username = 'John' //Get from api
  return {
    props: {username},
  }
}

Here, we are placing username in context’s Provider. It means the username is set in the context.

Component2 is placed inside the Provider, it means Component2 and all its childs will have access to the username. See the Component2 below.

// src/components.js
import React, {useContext} from "react";
import {UsernameContext} from "./context";

export function Component2() {
  return <div style={{border: '1px solid silver', width: '90%', height: '70vh'}}>
    <h2>Component2</h2>
    <Component3/>
  </div>
}

export function Component3() {
  return <div style={{border: '1px solid silver', width: '90%', height: '50vh'}}>
    <h3>Component3</h3>
    <Component4/>
  </div>
}

export function Component4() {
  const context = useContext(UsernameContext)
  return <div style={{border: '1px solid silver', width: '90%', height: '30vh'}}>
    <h4>Component4, {context.username}</h4>
  </div>
}

In Component4 we consume the context values by using a React hook called useContext(). We can also use the Consumer component of the context but the hook is a bit simpler. Both give the same result.

Output

context api with SSR in Next.js app