15

I'm using React's Context API to pass some context to lower level components.

I want to be able to run the component without a context provider (for testing). For this to work, I need to check whether there is a context provider around my component.

Example code:

const Wrapper = () => {

  // in my real app, there are some levels 
  // between the provider and the child component

  return <NameProvider value={name: 'User'}>
    <ChildComponent />
  </NameProvider>
}

const ChildComponent = () => {
  if (/* what can I put here ? */) {
    // inside Provider
    return <NameConsumer>
      {context => <span>{context.name}</span>}
    </NameConsumer>
  } else {
    // no provider available, e.g. in a test file
    return <span>Test Text</span>
  }
}

This question is not specifically about testing. There could be other situations where a component needs to work both inside and outside a context provider.

7
  • That is not a good idea.. Check this lib for this Commented Sep 4, 2018 at 13:46
  • This is not a good approach to modify the component itself for test purposes only. You'd better try to mock the context in test. Commented Sep 4, 2018 at 13:47
  • @ArupRakshit That api seems to use the old context, not the new context in React 16.3 Commented Sep 4, 2018 at 13:55
  • It's unclear what is the problem with importing Provider in the place where you define ChildComponent, especially if this is for testing purposes. Commented Sep 4, 2018 at 13:57
  • @estus In my case, the problem is that this Provider does not cooperate with Enzyme's mount function (produces errors). That said, the answer could be "there is no way to find out if we're in a context provider". Commented Sep 4, 2018 at 14:00

3 Answers 3

7

No official way is provided for checking whether there is <Provider> parent for <Consumer> child.

Generally, there is no difference if <Consumer> is inside or outside <Provider> with undefined value, it will be provided with undefined value in both cases.

It's possible to check current context value using internal _currentValue property, but this may result in false positives for undefined context value:

const ChildComponent = () => {
  if (NameConsumer._currentValue !== undefined) {
    // inside Provider
    return <NameConsumer>
      {context => <span>{context.name}</span>}
    </NameConsumer>
  } else {
    // no provider available, e.g. in a test file
    return <span>Test Text</span>
  }
}

Notice that this may not work as expected in asynchronous rendering, and relying on internals isn't recommended. A better way would be to check for test environment instead.

A more testable way is to use NameContext.Consumer consistently instead of NameConsumer, so Consumer property could be mocked in tests. Otherwise this may require to mock a module where NameConsumer is defined.

Sign up to request clarification or add additional context in comments.

Comments

1

You can set object-like default value (not primitive or null for safity) for context:

export const someContextDefValue = {}
export const someContext = createContext(someContextDefValue)

Now you can check context value:

const someContextValue = useContext(someContext)
if (someContextValue === someContextDefValue) {
    // Non under context
} else {
    // Context provider detected
}

It will work because we must set value to context provider (<someContext.Provider value={currentContextValue}>), but new object in value not equal with default value

1 Comment

Or you can set Symbol as default value
0

Just check the context. If it's undefined, then there's no Provider.

const myContext = useContext(theContext);

if ( myContext ) {
  return <div>I have a provider.</div>
}

return <div>I have no provider.</div>

I typically use it for components I want to work standalone and be part of a larger widget. See something like Google Maps where I want to hoist up the map context.

const __mapContext = useContext(MapContext);

if ( __mapContext ) {
  return <GoogleMap {...props} />
}

return (
  <GoogleMapProvider>
    <GoogleMap {...props} />
  </GoogleMapProvider>
);

4 Comments

This is not a reliable method, as many contexts will have default values. In fact, using TypeScript, at least with certain configurations, a default value will be enforced. You can see an example of what that looks like here: codesandbox.io/s/twilight-browser-j40wod?file=/src/App.tsx
Another method, set a property and check for that. e.g. if ( myContext?.somePropertyThatIsUnique ) and in your context createContext just initialize with { somePropertyThatIsUnique: true }.
As noted above, you will need to be sure that all created contexts have a default value of null, otherwise the guard will always pass.
Correct, you can createContext(undefined) to do that.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.