How to detect if context provider is present in a React app?

Viewed 511

Context

I'm working with a hybrid next.js and react-router app. Parts of the app are handled by react-router (hash-based), and parts of it by next.js router. There are common components which use hooks related to the current routing state (e.g. useLocation), which crash if the react-router provider wrapper is missing.

Problem

I would like to write a hook that returns either useLocation (from react-router-dom) or useRouter (from next.js), depending on whether it detect the react-router provider in the current context.

Then I would use this hook in common components, so that they work regardless of which context they're used in.

There is a similar solution for detecting whether to use useEffect or useLayoutEffect for SSR, called useIsomorphicLayoutEffect. I'm thinking that a similar approach could work in my case. However, feel free to suggest different solutions.

The error I'm getting is TypeError: useContext(...) is undefined. The react-router wrapper provides a context which is used by the useLocation hook. Therefore I believe a generic solution for detecting the context provider would be valid here.

Example

const fooCommonComponent = () => {
  // ❌ this only works when react-router-dom provider exists in the current context
  const { pathname } = useLocation(); 
  // ❌ this only works for next.js router
  const { pathname } = useRouter(); 

  // ✅ what i want
  const { pathname } = useCustomLocation();
};

const useCustomLocation = () => {
  // how to implement this?
};
1 Answers

I'm not very familiar with the Next.js side of things, but react-router-dom@6 has an useInRouterContext hook to return true/false if the component is rendered within a RRD routing context.

useInRouterContext

The useInRouterContext hooks returns true if the component is being rendered in the context of a <Router>, false otherwise. This can be useful for some 3rd-party extensions that need to know if they are being rendered in the context of a React Router app.

Here's an example implementation that works for at least the RRD side of things.

import { useInRouterContext, useLocation } from "react-router-dom";
import { useRouter } from "next/router";

const useCustomLocation = () => {
  const isInRRDContext = useInRouterContext();

  return (isInRRDContext ? useLocation : useRouter)() ?? {};
};

Edit how-to-detect-if-context-provider-is-present-in-a-react-app

Related