jackowsky
jackowsky

Reputation: 57

Gatsby Context API doesn't render its value

Trying to render state from Context API, but in console it shows as undefined and doesn't render anything.

here is Context file

import React, { useReducer, createContext } from "react"

export const GlobalStateContext = createContext()
export const GlobalDispatchContext = createContext()

const initialState = {
  isLoggedIn: "logged out",
}

function reducer(state, action) {
  switch (action.type) {
    case "TOGGLE_LOGIN":
      {
        return {
          ...state,
          isLoggedIn: state.isLoggedIn === false ? true : false,
        }
      }
      break

    default:
      throw new Error("bad action")
  }
}

const GlobalContextProvider = ({ children }) => {
  const [state, dispatch] = useReducer(reducer, initialState)
  return (
    <GlobalStateContext.Provider value={state}>
      {children}
    </GlobalStateContext.Provider>
  )
}

export default GlobalContextProvider

and here is where value should be rendered

import React, { useContext } from "react"
import {
  GlobalStateContext,
  GlobalDispatchContext,
} from "../context/GlobalContextProvider"

const Login = () => {
  const state = useContext(GlobalStateContext)
  console.log(state)
  return (
    <>
      <GlobalStateContext.Consumer>
        {value => <p>{value}</p>}
      </GlobalStateContext.Consumer>
    </>
  )
}

export default Login

I tried before the same thing with class component but it didn't solve the problem. When I console log context it looks like object with undefined values.

Any ideas?

Upvotes: 0

Views: 991

Answers (1)

piticent123
piticent123

Reputation: 303

The Context API In General

From the comments, it seems the potential problem is that you're not rendering <Login /> as a child of <GlobalContextProvider />. When you're using a context consumer, either as a hook or as a function, there needs to be a matching provider somewhere in the component tree as its parent.

For example, these would not work:

<div>
    <h1>Please log in!</h1>
    <Login />
</div>
<React.Fragment>
    <GlobalContextProvider />
    <Login />
</React.Fragment>

because in both of those, the Login component is either a sibling of the context provider, or the provider is missing entirely.

This, however, would work:

<React.Fragment>
    <GlobalContextProvider>
        <Login />
    </GlobalContextProvider>
</React.Fragment>

because the Login component is a child of the GlobalContextProvider.

Related To Gatsby

This concept is true regardless of what library or framework you're using to make your app. In Gatsby specifically there's a little bit of work you have to do to get this to work at a page level, but it's possible.

Let's say you have a Layout.jsx file defined, and the following page:

const Index = () => (
    <Layout>
        <h1>{something that uses context}</h1>
    </Layout>
)

You have 2 options:

  1. The easier option is to extract that h1 into its own component file. Then you can put the GlobalContextProvider in the Layout and put the context consumer in the new component. This would work because the h1 is being rendered as a child of the layout.
  2. Is to do some shuffling.

You might be inclined to put the Provider in the layout and try to consume it in the page. You might think this would work because the h1 is still being rendered as a child of the Layout, right? That is correct, but the context is not being consumed by the h1. The context is being rendered by the h1 and consumed by Index, which is the parent of <Layout>. Using it at a page level is possible, but what you would have to do is make another component (IndexContent or something similar), consume your context in there, and render that as a child of layout. So as an example (with imports left out for brevity):

const Layout = ({children}) => (
    <GlobalContextProvider>
        {children}
    </GlobalContextProvider>
);

const IndexContent = () => {
    const {text} = useContext(GlobalStateContext);
    return <h1>{text}</h1>;
}

const Index = () => (
    <Layout>
        <IndexContent />
    </Layout>
);

Upvotes: 2

Related Questions