React custom hook spit Error " Error: Should have a queue. This is likely a bug in React. Please file an issue"

Viewed 792

I have react component which needs to consume a customized react hook from within the component.

However, this hook should only be called when a feature toggle is enabled. I understand this is sort of anti-pattern as it is against the rule of hooks here: https://reactjs.org/docs/hooks-rules.html

So my component file is roughly in this structure:

    const someFeatureToggle = useSomeFeatureToggleHook(React);
    const callBackMethod = ()=>{
        // doing the logic
     }
    const someRef1 = React.useRef();
    const someOtherRef = React.useRef();
    ...
    There are lots of useState( ) here

    return (
        JSX
      )

For the customized hook:

export default function myCustomizedHook(topics, messagesReceivedFn, subscriptionOptions = {}) {
  if (!isValidTopics(topics)) {
    throw new Error(`Topics arg is invalid - Arg ${JSON.stringify(topics)}`);
  }
  const [someSubTopics] = useState([topics].flat());

  const context = useContext(SomeEventContext);
  if (isUndefined(context)) {
    throw new Error(`${customizedHook.name} must be used within SomeProvider`);
  }
  const { connectionStatus, connector } = context;
  const isConnectorConnected = connector?.connected ?? false;
  const isConnectorReconnecting = connector?.reconnecting ?? false;

  const messageReceivedHandler = useCallback(
    (receivedTopic, message) => {
      if (subscribedTopics.some((topic) => matches(topic, receivedTopic))) {
        messagesReceivedFn?.(receivedTopic, message);
      }
    },
    [messagesReceivedFn, subscribedTopics]
  );

  useEffect(() => {
    isConnectorConnected && connector?.on(CLIENT_EVENTS.MESSAGE, messageReceivedHandler);

    return () => {
      connector?.off(CLIENT_EVENTS.MESSAGE, messageReceivedHandler);
    };
  }, [messageReceivedHandler, connector, isConnectorConnected]);

  useDeepCompareEffect(() => {
    isConnectorConnected && connector.subscribe(subscribedTopics, subscriptionOptions);

    return () => {
      subscribedTopics && connector?.unsubscribe(subscribedTopics);
    };
  }, [connector, isConnectorConnected, subscribedTopics, subscriptionOptions]);

  return { isConnected: isConnectorConnected, isReconnecting: isConnectorReconnecting, connectionStatus, subscribedTopics };

Now the error trace is like this:

Uncaught Error: Should have a queue. This is likely a bug in React. Please file an issue.
    at updateReducer (react-dom.development.js:15255:15)
    at updateState (react-dom.development.js:15671:12)
    at Object.useState (react-dom.development.js:16472:18)
    at useState (react.development.js:1533:23)
    at customizedHook (customizedHook.js:28:38)
    at componentThatConsumeHook (componentThatConsumeHook.js:67:99)
    at renderWithHooks (react-dom.development.js:15015:20)
    at updateFunctionComponent (react-dom.development.js:17386:22)
    at beginWork (react-dom.development.js:19093:18)
    at HTMLUnknownElement.callCallback (react-dom.development.js:3942:16)

and there is this warning from dev console in the browser:

Warning: React has detected a change in the order of Hooks called by myComponent. This will lead to bugs and errors if not fixed. For more information, read the Rules of Hooks: https://reactjs.org/link/rules-of-hooks

   Previous render            Next render
   ------------------------------------------------------
1. useRef                     useRef
2. useState                   useState
3. useEffect                  useEffect
4. useRef                     useState
   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

line 28 of customizedHook is point to this line below:

const [someSubTopics] = useState([topics].flat());

This is how I consume the hook in the component:

const result = (!!featureToggles.Flag) && customHook(arg1, callbackMethod);

I am scratching my head here as I have no clue why this is happening, any thought or advice would be greatly appreciated !!!!

1 Answers

That's because you're calling hook inside a condition.

const result = (!!featureToggles.Flag) && customHook(arg1, callbackMethod);

is basically the same as

let result = false
if (!!featureToggles.Flag){
  result = customHook(arg1, callbackMethod);
}

From React document:

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

What you could do is to pass featureToggles into your custom hook and do the check inside that instead.

Make sure your condition check is at the bottom of your hook, below every hook call.

// pass featureToggles here
export default function myCustomizedHook(featureToggles, topics, messagesReceivedFn, subscriptionOptions = {}) {
  // your code here

  useDeepCompareEffect(() => {
    isConnectorConnected && connector.subscribe(subscribedTopics, subscriptionOptions);

    return () => {
      subscribedTopics && connector?.unsubscribe(subscribedTopics);
    };
  }, [connector, isConnectorConnected, subscribedTopics, subscriptionOptions]);

  // make sure to do the check at the bottom, below every hook call
  if (!!featureToggles.Flag) {
    // handle this feature toggle
    return null
  }

  return { isConnected: isConnectorConnected, isReconnecting: isConnectorReconnecting, connectionStatus, subscribedTopics };
Related