21

In my Next.js app, I need to make an API call that will return a JSON response which needs to be used by all the components. Hence I will avoid multiple calls for the same API in my components. What is the proper way to achieve this?

Jayanth
  • 485
  • 3
  • 9
  • 17
  • 1
    Please give add more context include some code. It's very hard to answer as it's very broad – Arnaud Claudel Jun 24 '20 at 13:49
  • @ArnaudClaudel Thanks for your kind response. I need some help on maintaining global data for all the components which I fetched from an API call – Jayanth Jun 24 '20 at 13:56

2 Answers2

27

Depends on the scope of the application.
If it's a large one, you should probably adopt a state manager like Redux, as Moshin Amjad says.

If it's a smaller app, you could try to manage it with the React context APIs.
I'll make an example in the simplest way, using functional components, levering getStaticProps instead of getInitialProps in order to get static generated pages.

Start creating a simple context

libs/context.js

import React from "react";
export const Context = React.createContext();

Then populate a useState hook (or better, a useReducer depends on how the data is structured) with the data coming from getStaticProps (or getInitialProps) and then pass it to the Context Provider.

pages/index.js

import React from 'react'
import { Context } from "../libs/context.js"

import Title from "../components/Title"
import Button from "../components/Button"

// data will be populated at build time by getStaticProps()

function Page({ data }) {
    const [ context, setContext ] = React.useState(data)
    return (
        <Context.Provider value={[context, setContext]}>
            <main>
                <Title />
                <Button />
            </main>
        </Context.Provider>
    )
}

export async function getStaticProps(context) {

  // fetch data here
  const data = await fetchData()

  // Let's assume something silly like this:
  // {
  //     buttonLabel: 'Click me to change the title',
  //     pageTitle: 'My page'
  // }
  
  return {
    props: {
       data
    }, // will be passed to the page component as props
  }
}

And finally consume it (or change it!) in any child of the provider.

components/Title.js

import React, { useContext } from "react"
import { Context } from "./Context"

export default function MyComponent() {
   const [context, setContext] = useContext(Context)
   
   return (
       <h1>{context.pageTitle}</h1>
   )
}

components/Button.js

import React, { useContext } from "react"
import { Context } from "./Context"

export default function MyComponent() {
   const [context, setContext] = useContext(Context)
   
   function changeTitle() {
      preventDefault();
      setContext(oldContext => ({ 
          ...oldContext, 
          pageTitle: 'New page title!' 
      }))
   }

   return (
       <div>
          <button onClick={changeTitle}>{context.buttonLabel}</button>
       </div>
   )
}

It's untested but you get the idea.
Eventually you could move the context provider in a High Order Component to wrap every page with, or even in pages/_app.js if you need the data at a higher level.

Remember, if the app scales up, you should consider something like Redux.

0

Use redux to store your state in global store so it can be accessed by multiple components this is common pattern for your kind of use case. There is also next-wrapper for it here is the link next-redux-wrapper redux

Mohsin Amjad
  • 1,045
  • 6
  • 14