Conditionally execute useContext in React
Asked Answered
R

2

6

I am looking to conditionally call useContext. This is my original code, which runs correctly, but fails tslint.

Component that calls useResourceScope - say Component1

import { useEffect } from 'react';

export function useSubscribeListItemStore(
  inputResources?: ResourceScope
) {
  const resources = inputResources || useResourceScope();
  const listItemStore = resources.consume(listItemStoreKey);
  
  useEffect(function subscribeListItemStore() {
    // do some logic
  }, []);
}

Component that defines useResourceScope - say Component2

import { createContext } from 'preact';
import { useContext } from 'preact/hooks';

export const ResourceScopeContext = createContext<ResourceScope | undefined>(undefined);

export function useResourceScope(): ResourceScope {
  const resources = useContext(ResourceScopeContext);
  if (!resources) {
    throw new Error(
      'error message'
    );
  }
  return resources;
}

This gives me a tslint error: (react-hooks-nesting) A hook cannot be used in a conditional expression

This is my attempt at solving this, but does not work. It does not run correctly and fails at the useContext line in Component2 My thought was that since useContext must be called to fix the tslint errors, I decided to conditionally call the useContext with different created contexts. If the variable inputResources is passed in, it calls useContext with a placeholder context called PlaceHolderContext, which it ignores and returns inputResources. If not, it does useContext using the intended ResourceScopeContext value.

Component that calls useResourceScope - say Component1

import { useEffect } from 'react';

export function useSubscribeListItemStore(
  inputResources?: ResourceScope
) {
  const resources = useResourceScope(inputResources);
  const listItemStore = resources.consume(listItemStoreKey);
  
  useEffect(function subscribeListItemStore() {
    // do some logic
  }, []);
}

Component that defines useResourceScope - say Component2

import { createContext } from 'preact';
import { useContext } from 'preact/hooks';

export const ResourceScopeContext = createContext<ResourceScope | undefined>(undefined);

export function useResourceScope(inputResources?: ResourceScope): ResourceScope {
  const PlaceHolderContext = createContext({ foo: 'bar' });
  const createdContext = inputResources ? PlaceHolderContext : ResourceScopeContext;

  const resources = useContext(createdContext as any);

  if (inputResources) {
    return inputResources;
  }

  if (!resources) {
    throw new Error(
      'error message'
    );
  }
  return resources as any;
}

Could you suggest how I can resolve tslint error here without breaking it?

Raimes answered 2/6, 2021 at 1:50 Comment(0)
N
0

Rather than conditionally calling the hook, can you conditionally use its return value?

import { useEffect } from 'react';

export function useSubscribeListItemStore(
  inputResources?: ResourceScope
) {
  const contextResources = useResourceScope();
  const resources = inputResources || contextResources;

  const listItemStore = resources.consume(listItemStoreKey);
  
  useEffect(function subscribeListItemStore() {
    // do some logic
  }, []);
}
Natant answered 2/6, 2021 at 2:2 Comment(7)
thanks for the idea. I tried doing that, but it gives me the same runtime error - Uncaught TypeError: Cannot read property 'context' of undefined when I click a button. The error originates from the line const resources = useContext(ResourceScopeContext);Raimes
Do you have the context provider higher up the component tree?Natant
you are right, I don't. But without changing the context provider to be available up the component tree, is there a change I can make in const resources = useContext(ResourceScopeContext); so as to not result in an error? I am not going to be using the resources returned here. I can pass in the inputResources like in my original attempt and make it return that, I only want it to not result in an error.Raimes
Hmm, I'm not quite sure what you're asking. the useContext hook only works in components lower in a React component tree from its provider.Natant
right! My apologies for not being clear. I was asking - can I do const notGoingToBeUsingThisResource = useContext(something that does not result in an error); Please let me know if this does not make sense, and I can go back and study useContext.Raimes
No, I believe you'll always get an error if you use the useContext hook without a providerNatant
Let us continue this discussion in chat.Raimes
D
1

First off, https://reactjs.org/docs/hooks-rules.html read the bold text:

Only Call Hooks at the Top Level

Don’t call Hooks inside loops, conditions, or nested functions.

That being said, https://aganglada.com/blog/how-to-break-the-rules-with-conditional-hooks

Dietrich answered 2/6, 2021 at 1:57 Comment(1)
linked site has disappeared.Worldbeater
N
0

Rather than conditionally calling the hook, can you conditionally use its return value?

import { useEffect } from 'react';

export function useSubscribeListItemStore(
  inputResources?: ResourceScope
) {
  const contextResources = useResourceScope();
  const resources = inputResources || contextResources;

  const listItemStore = resources.consume(listItemStoreKey);
  
  useEffect(function subscribeListItemStore() {
    // do some logic
  }, []);
}
Natant answered 2/6, 2021 at 2:2 Comment(7)
thanks for the idea. I tried doing that, but it gives me the same runtime error - Uncaught TypeError: Cannot read property 'context' of undefined when I click a button. The error originates from the line const resources = useContext(ResourceScopeContext);Raimes
Do you have the context provider higher up the component tree?Natant
you are right, I don't. But without changing the context provider to be available up the component tree, is there a change I can make in const resources = useContext(ResourceScopeContext); so as to not result in an error? I am not going to be using the resources returned here. I can pass in the inputResources like in my original attempt and make it return that, I only want it to not result in an error.Raimes
Hmm, I'm not quite sure what you're asking. the useContext hook only works in components lower in a React component tree from its provider.Natant
right! My apologies for not being clear. I was asking - can I do const notGoingToBeUsingThisResource = useContext(something that does not result in an error); Please let me know if this does not make sense, and I can go back and study useContext.Raimes
No, I believe you'll always get an error if you use the useContext hook without a providerNatant
Let us continue this discussion in chat.Raimes

© 2022 - 2024 — McMap. All rights reserved.