Search code examples
javascriptreactjsreact-router-dom

How to reuse react route dom Link's to parameters to onClick


I am using react-router-dom Link and NavLink v5 and want to short circuit the to parameter values to pass to the onClick.

Here is my situation:

In my application - so far only the to parameter is used for react Link and NavLink. However I have a new requirement that I need to send some events to the outer iFrame while clicking the Link or NavLink.

So far my code was like this:

const addVolumeLink = location => ({
    pathname: location.pathname + "/add-volume",
    state: {
        from: location.pathname
    }
});

<Link to={addVolumeLink}>New volume</Link>

However when user is clicking on the New Volume, I also need to send an event to the outer iFrame. So naively I altered this code as follows by adding the onClick listerner.

const addVolumeLink = location => ({
    pathname: location.pathname + "/add-volume",
    state: {
        from: location.pathname
    }
});

export const nagivateToParentiFrame = (payload) => {
    window.parent.postMessage({
        type: 'SERVICE:NAVIGATE',
        payload,
    }, "*")
};

<Link to={addVolumeLink} 
      onClick={() => nagivateToParentiFrame({
               pathname: `${window.location.pathname}/add-volume`,
               state: {from: window.location.pathname}})}>
       New volume
</Link>

What I am looking for: if there is a way to short-circuit the to parameter to the onClick. (note: it's a function call here that uses location .. can be a function call or can be an object as well). Reason: There are 100 of places this onClick in the code I need to write. So I am thinking of having my own Link component and just replace the import instead of everywhere writing this onClick (as my pathname and state shall be same).

Something like this I tried (which did not worked out):

// The component which is not working.

import {Link} from "react-router-dom"

const nagivateToParentiFrame = (payload) => {
    window.parent.postMessage({
        type: 'SERVICE:NAVIGATE',
        payload,
    }, "*")
};

export const StyledLinkWithIframeEvent = ({ children, className, style, color, type, large, disabled, tooltip, to, ...rest }) => {
    return <Link
        to={to}
        onClick={() => nagivateToParentiFrame(to)}
        children={children}
        className={className}
        style={style}
        color={color}
        type={type}
        large={large}
        disabled={disabled}
        tooltip={tooltip}
        rest={rest}>
    </Link>
}

The usage of the component:

import {StyledLinkWithIframeEvent as StyledLink} from "./myLink"

const addVolumeLink = location => ({
    pathname: location.pathname + "/add-volume",
    state: {
        from: location.pathname
    }
});

const myMainComponent = () => {
   return (<StyledLink to={addVolumeLink}>
         New volume
   </StyledLink>);
}

Solution

  • Create a custom link component that adds its own onClick handler that calls your navigateToParentiFrame function and is passed the result of the passed to prop.

    export const navigateToParentiFrame = (payload) => {
      window.parent.postMessage({
        type: 'SERVICE:NAVIGATE',
        payload,
      }, "*")
    };
    
    import { Link as BaseLink, useLocation } from 'react-router-dom';
    import { navigateToParentiFrame } from '../path/to/navigateToParentiFrame';
    
    const Link = props => {
      const location = useLocation();
      
      return (
        <BaseLink
          {...props}
          onClick={navigateToParentiFrame.bind(
            null,
            typeof props.to === "function"
              ? props.to(location)
              : props.to
          )}
        >
          {props.children}
        </BaseLink>
      );
    };
    
    const addVolumeLink = location => ({
      ...location,
      pathname: location.pathname + "/add-volume",
      state: {
        ...location.state,
        from: location.pathname,
      }
    });
    
    <Link to={addVolumeLink}>New volume</Link>
    

    const {
      BrowserRouter: Router,
      Switch,
      Route,
      Link: BaseLink,
      useLocation,
    } = ReactRouterDOM;
    
    const navigateToParentiFrame = (payload) => {
      console.log("window.parant.postMessage", {
        type: 'SERVICE:NAVIGATE',
        payload,
      }, "*");
    };
    
    const Link = props => {
      const location = useLocation();
      
      return (
        <BaseLink
          {...props}
          onClick={navigateToParentiFrame.bind(
            null,
            typeof props.to === "function"
              ? props.to(location)
              : props.to
          )}
        >
          {props.children}
        </BaseLink>
      );
    };
    
    const addVolumeLink = location => ({
      ...location,
      pathname: location.pathname + "add-volume",
      state: {
        ...location.state,
        from: location.pathname,
      }
    });
    
    //<Link to={addVolumeLink}>New volume</Link>
    
    const App = () => {
     return (
        <React.Fragment>
          <ul>
            <li>
              <BaseLink to="/">Home</BaseLink>
            </li>
            <li>
              <Link to={addVolumeLink}>New volume</Link>
            </li>
          </ul>
        
          <Switch>
            <Route path="/add-volume" render={() => <h1>Add Volume</h1>} />
            <Route path="/" render={() => <h1>Home</h1>} />
          </Switch>
        </React.Fragment>
      );
    }
    
    const rootElement = document.getElementById("root");
    const root = ReactDOM.createRoot(rootElement);
    
    root.render(
      <React.StrictMode>
        <Router basename="/js">
          <App />
        </Router>
      </React.StrictMode>
    );
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.production.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.production.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-router-dom/5.3.4/react-router-dom.min.js" integrity="sha512-XJ25BbjmZQpaGkOJaDFXVJqfsBebv2aDLsBF3qT6zoC/gVj7XLuefNRzcgmlc5admYuODaYDrap8jzVyOsYFrw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
    <div id="root" />