Search code examples
reactjsreact-hooksreact-routeruse-effect

useffect inside <outlet> component does not trigger


Parent component:

  <Main  props... >
   <LinksArray />
   <Outlet context={investorId}/>
  </Main>

outlet component

const NewBoards: React.FC = () => {
     let { boardId } = useParams();
     // this does not
     useEffect(() => {
         console.log('ue trigger page change',boardId )
     }, []); 

     // this triggers (because of an argument 
     useEffect(() => {
       console.log('ue trigger page change',boardId )
     }, [boardId ]);
    
     return (

     <>
      {console.log('>rerender')}
      <p> newBoards</p>
      <p>{boardId}</p>

    </>
     )

}

NewBoards is an outlet element, I would love for useEffect to trigger on a ROUTE (ex. boards/123 to boards/345 ) change, without passing boardId, however useEffect does not trigger on the address change unless I`m passing boardId to the dependency array. boardId param does change.

Also I would like to know why that useEffect does not trigger. I can't find anything related to it on the react router v6 official documentation

edit:

I have also noticed that states are saved. States inside outlet component( NewBoards ) do not refresh to initial ones.

edit ( router definition ) :


  {
    path: '/boards/',
    element: authorized ?  <Boards1 />  : <Login />,
    children: [{
      path: '/boards/:boardId',
      element: authorized ? <NewBoards /> : <Login />,
    }]
  },

Solution

  • From what I see from your comments you've seriously misunderstood what is happening between the Outlet and the nested Route components that are rendering their content, i.e. element prop, into it.

    Assuming authorized is true then the following route config:

    {
      path: '/boards/',
      element: authorized ?  <Boards1 />  : <Login />,
      children: [{
        path: '/boards/:boardId',
        element: authorized ? <NewBoards /> : <Login />,
      }]
    },
    

    will produce the following rendered routes:

    <Routes>
      ...
      <Route path="/boards" element={<Boards1 />}>
        <Route path="/boards/:boardId" element={<NewBoards />} />
      </Route>
      ...
    </Routes>
    

    Where I think your understanding goes awry is in thinking that when the URL path changes from "/boards/123" to "/boards/345" that the Boards1 component rendered on "/boards" will rerender and remount the Outlet. It won't. This means that the Outlet it is rendering also doesn't do anything other than output the result of the currently matched route.

    A second point of confusion/misudnerstanding on your part is thinking that when the URL path changes from "/boards/123" to "/boards/345" that <Route path="/boards/:boardId" element={<NewBoards />} /> will unmount and remount a new instance of the NewBoards component. This is also not the case. The NewBoards component will remain mounted and will simply rerender. This is an optimization by react-router-dom as it's a lot more work to tear down and remount a component than it is to simply rerender it with some different props/context/etc.

    The routed components using the route path params necessarily need to "listen" to changes to the params if they need to issue side-effects based on the param values. This is why the useEffect hook with empty dependency array runs only once when the NewBoards component was mounted (not each time the route changes) and the useEffect hook with the boardId param as a dependency correctly reruns each time the boardId value changes.

    const NewBoards: React.FC = () => {
      const { boardId } = useParams();
    
      // Run this when component mounts, occurs once per mounting
      useEffect(() => {
        console.log('ue trigger page change', boardId);
      }, []); 
    
      // Run this when the `boardId` param updates 
      useEffect(() => {
        console.log('ue trigger page change', boardId);
      }, [boardId]);
    
      // Run this each render, i.e. no dependency array at all!
      useEffect(() => {
        console.log('>rerender');
      });
    
      return (
        <>
          <p>newBoards</p>
          <p>{boardId}</p>
        </>
      );
    };