Search code examples
reactjsreact-hooksreact-router-dom

Using useParams() in the top level


I'm trying to use useParams() in my App component because I need it in two different child components.

But useParams() returns *: "tenants/rdyTupPulEab6mztoLvnQ/projects/0/workspace" not actually able to destructure the tenantId.

I assume this is because App isn't rerendering when the url changes, but I don't want to put two useParams() in both children and send the data back up to app. This is the best place for it to go, but not sure how to get useParams() to destructure the data correctly.

How can I do this, or what alternatives are there?

MRE:

function App() {
   console.log(useParams())

   useEffect(() => {
      (api call that needs the tenantId from useParams())
   })

   return (
      <Routes>
         <Route path="/tenants/:tenantId/workspace" 
         element={<Workspace/>}/>
         <Route path="/tenants/:tenantId/setup" element=. 
        {<Setup/>}/>
      </Routes>
   )
}

The console.log returns {*: 'tenants/rdyTupPulEab6mztoLvnQ/projects/0/workspace'}.

I need it to return {*: 'tenants/rdyTupPulEab6mztoLvnQ/projects/0/workspace', tenantId: 'rdyTupPulEab6mztoLvnQ'}


Solution

  • The App component can't access the route path params of any of the routes the Routes component is managing. The options you have are to use the matchPath utility to find a "match" to extract the tenentId parameter value.

    Something like:

    function App() {
      const match = useMatch("/tenants/:tenantId/*");
    
      useEffect(() => {
        if (match) {
          const { tenantId } = match.params;
          // ... business logic using tenentId
        }
      }, [match]);
    
      return (
        <Routes>
          <Route path="/tenants/:tenantId/workspace" element={<Workspace />} />
          <Route path="/tenants/:tenantId/setup" element={<Setup />} />
        </Routes>
      );
    }
    

    An alternative is to create an intermediate layout component that can use the useParams hook.

    Example:

    import { Outlet, useParams } from 'react-router-dom';
    
    export const TenantIdLayout = () => {
      const { tenantId } = useParams();
    
      useEffect(() => {
        if (tenantId) {
          // ... business logic using tenentId
        }
      }, [tenantId]);
    
      return <Outlet />;
    };
    
    function App() {
      return (
        <Routes>
          <Route element={<TenantIdLayout />}>
            {/* "tenantId" routes */}
            <Route path="/tenants/:tenantId/workspace" element={<Workspace />} />
            <Route path="/tenants/:tenantId/setup" element={<Setup />} />
          </Route>
    
          {/* non-"tenantId" routes */}
        </Routes>
      );
    }