Search code examples
javascriptreactjsreact-context

React Context without nesting a provider?


I'm wondering if it's possible to create a context and consume it without passing the context down the whole section of the dom tree.

To that extent I've created the following example:

./components/count-context.js

import * as React from 'react'
const CountContext = React.createContext({count : 0} )


const CountContextProvider = (props) => {
  const [count, setCount] = React.useState(0);
  
  const incrementCount = () => {
    console.log("increment count", count)
    setCount(count + 1);
  };

  const decrementCount = () => {
    setCount(count - 1);
  }

  return (
    <CountContext.Provider value={{ count, setCount, incrementCount, decrementCount }}>
      {props.children}
    </CountContext.Provider>
  );
}

const CountDisplayNoProvider = (props) => {
  const { count } = React.useContext(CountContext)
  return (
      <p>{count}</p>
  )
}

const CountDisplaySelfProvided = (props) => {
  const { count } = React.useContext(CountContext)
  return (
    <CountContextProvider>
      <p>{count}</p>
    </CountContextProvider>
  )
}

const IncrementCountButton = (props) => {
  const { count, incrementCount, setCount } = React.useContext(CountContext)
  console.log(`count is a `, typeof(count))
  console.log(`incrementCount is a `, typeof(incrementCount))
  console.log(`setCount is a `, typeof(setCount))
  return (
    <button onClick={incrementCount}>IncrementCountButton</button>
  )
}
 

export {
  CountContextProvider,
  CountDisplayNoProvider,
  CountDisplaySelfProvided,
  IncrementCountButton
}


And: ./App.js

import './App.css';

import { CountContextProvider, CountDisplaySelfProvided, CountDisplayNoProvider, IncrementCountButton} from './components/count-context'

function App() {

  return (
    <div className="App">
      <p>NO CONTEXT</p>
        CountDisplayNoProvider: <CountDisplayNoProvider />
        <IncrementCountButton />

        <br />

        CountDisplaySelfProvided: <CountDisplaySelfProvided />
        <IncrementCountButton />

      <CountContextProvider>
        <p>CountDisplayNoProvider inside CountContextProvider</p>
        <CountDisplayNoProvider />
        <IncrementCountButton />
      </CountContextProvider>
    </div>
  );
}

export default App;

The funny thing is that the CountDisplayNoProvider and CountDisplaySelfProvided both show a number: 0

but count-context.js:34 incrementCount is a undefined count-context.js:35 setCount is a undefined

--- so why is it that the count gets passed to NO CONTEXT, but not the functions?

And even stranger, why is it that I can't put the provider in the count's own component? (CountDisplaySelfProvided)

Thank you!!

Screenshot of the rendered output after rage clicking all the buttons Screenshot of the rendered output after rage clicking all the butt


Solution

  • I think I understand as to why those other components did get some value like 0 from the context. While we do need the provider to consume the values, different things happen to the other components.

    First CountDisplayNoProvider

    This only consumes the initial value of count which you've provided when creating the Context. const CountContext = React.createContext({count : 0}). However, if you've tried consuming the other functions you've passed on the Provider, then it would only return undefined because of it not being set initially when the context was created.

    CountDisplaySelfProvided

    This explains the same thing with the First <CountDisplayNoProvider/>. The problem with this one is you're already using the Context then providing it inside the return.

    In this case, you're consuming it before you get everything from the Provider. So you wouldn't be able to use the functions here too. While you did add a Provider, useContext runs first thus the undefined functions.

    If it had children who would later consume it via useContext, then it should definitely work and have their own count, setCount & everything else you've provided.