skip to Main Content

App.js

const router = createBrowserRouter([
  {
    path: '/',
    element: <MainHeader />, 
    children:[
      { index: true, element: <Home />, loader :loaderHome }, 
    ]
  }
])

const App = () => { 
  return <RouterProvider router={router} />; 
}

export default App;

MainHeader.js

import { Outlet, NavLink, useLocation } from 'react-router-dom';

const MainHeader = (props) => { 
  const testRef = useRef();
  const location = useLocation();
  return (
    <>
      <header className={styles['main-header']}>
        <NavLink to={location.pathname==='/' ? testRef.current.testHandler : '/event'}>
          Home3
        </NavLink>
        <Login />
      </header>
      <Outlet innerRef={testRef} />
    </>
  );
}

export default MainHeader;

Home.js

import React, { useRef, useImperativeHandle } from 'react';

const Home = React.forwardRef((props, ref) =>
  useImperativeHandle(ref, () => {
    return {
      testHandler: testHandler
    }
  })

  // I had to useCallback to prevent this function from reloading when the useEffect loads
  const testHandler=async(e)=>{
    console.log('enter test handler')
  }

  return (
    ............
  );
})

export default Home;

What I want – in MainHeader.js, NavLink when clicked, if the route is '/' then call function testHandler defined in Home.js (child) otherwise go to route '/event'.

I tried doing this using forwardRef as with function components, this is what I have thus far but it isn’t working. I tried passing a ref in the Outlet component in MainHeader.js to Home.js to call the testHandler function.

Any ideas? Do I have to modify App.js? Is there any other way to call the testHandler function from MainHeader.js?

2

Answers


  1. It looks like you’re on the right track with using React.forwardRef() to pass a ref from the parent MainHeader component to the child Home component. However, there are a couple of issues with your current implementation.

    Firstly, you’re trying to access testHandler before it’s defined in Home. You should define testHandler before you try to pass it through the ref. Additionally, since you’re passing testHandler through a ref, you’ll need to wrap it in useCallback() to ensure that the same function instance is used on every render.

    Secondly, in MainHeader, you’re trying to call testRef.current.testHandler directly, but testRef.current will be null until Home has mounted. You’ll need to add a check to ensure that testRef.current is defined before trying to call testHandler.

    import { useLocation } from 'react-router-dom';
    import { useRef } from 'react';
    import Login from './Login';
    
    const MainHeader = (props, ref) => {
      const testRef = useRef();
      const location = useLocation();
    
      const handleClick = () => {
        if (testRef.current) {
          testRef.current.testHandler();
        }
      };
    
      return (
        <>
          <header className={styles['main-header']}>
            <NavLink to={location.pathname === '/' ? '#' : '/event'} onClick={handleClick}>
              Home3
            </NavLink>
            <Login />
          </header>
          <Outlet innerRef={testRef} />
        </>
      );
    };
    
    export default React.forwardRef(MainHeader);
    

    Home.js:

    import { forwardRef, useImperativeHandle, useCallback } from 'react';
    
    const Home = forwardRef((props, ref) => {
      const testHandler = useCallback(() => {
        console.log('enter test handler');
      }, []);
    
      useImperativeHandle(ref, () => ({
        testHandler,
      }));
    
      return <div>Home Component</div>;
    });
    
    export default Home;
    

    Here, MainHeader takes a ref parameter and passes it to Outlet as innerRef. In handleClick(), we check if testRef.current is defined before calling testHandler() on it.

    In Home, we define testHandler using useCallback(), and pass it through the ref using useImperativeHandle().

    I hope this works for you.

    Login or Signup to reply.
  2. It’s a huge React anti-pattern to call functions defined in other React components, typically indicative of a code smell.

    Define testHandler higher in the ReactTree and pass down to the components where you are wanting to call it.

    Also, testHandler isn’t a valid NavLink to prop value, so you will have problems trying to do what you are trying to do anyway. Add an onClick handler to the "/event" link that conditionally cancels the navigation action and calls testHandler.

    Example:

    App.jsx

    export default function App() {
      const testHandler = async (e) => {
        console.log("enter test handler");
      };
    
      const router = createBrowserRouter([
        {
          path: "/",
          element: <MainHeader {...{ testHandler }} />,
          children: [
            {
              index: true,
              element: <Home {...{ testHandler }} />,
              loader: loaderHome
            },
            ...
          ]
        }
      ]);
    
      return <RouterProvider router={router} />;
    }
    

    MainHeader

    const MainHeader = ({ testHandler }) => {
      const location = useLocation();
      return (
        <>
          <header className={styles["main-header"]}>
            <NavLink
              to="/event"
              onClick={(e) => {
                if (location.pathname === "/") {
                  e.preventDefault();
                  testHandler();
                }
              }}
            >
              Home3
            </NavLink>
            <Login />
          </header>
          <Outlet />
        </>
      );
    };
    

    Home

    const Home = ({ testHandler }) => {
      return (
        <div>
          <h1>Home</h1>
    
          ...
    
          <button type="button" onClick={testHandler}>
            testHandler
          </button>
    
          ...
        </div>
      );
    };
    

    Edit router-6-calling-child-component-function-from-the-parent-component

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search