Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to implement listen function in react-router-dom v6?

I'm updating a react project's packages, implemented with react-router-dom v5. I've updated react-router-dom to v6. I've used this.props.history in a lot of components, and because of that, I don't need to change its structure, so I decided to create a wrapper that has that functionality and pass the components to it as a prop to don't change a lot of code. Here's the wrapper:

import * as React from 'react';
import { useLocation, useNavigate, useNavigationType, useParams } from "react-router";

export function withRouter(Child) {
  return (props) => {
    const [callBack,setCallBack] = React.useState();
    const location = useLocation();
    const navigate = useNavigate();
    const action = useNavigationType();
    const params = useParams();
    const match = {
      params: params
    }
    const history = {
      push: (url, onMount) => {
        if (onMount)
          setTimeout(() => navigate(url), 100)
        else
          navigate(url);

      },
      listen: (callback) => {
        setCallBack(callback);
      }
    }
    React.useEffect(() => {
      if (callBack) {
        callBack(location,action)
      }
    }, [location])

    return <Child {...props} history={history} location={location} match={match} />;
  }
}

I've used my wrapper in a component like this:

function action(params) {
    return (
        <Layout {...params}>
            <PageInfo title={Utils.i18n('notificationList', true)} icon="ti-bell"/>
            <NotificationList {...params}/>
        </Layout>
    )
}

export default withRouter(action);

And Here's the listen function on componentDidMount of the wrapped component:

componentDidMount(): void {
        this.props.history.listen((location, action) => {
            const urlSearchParams = new URLSearchParams(window.location.search);
            const params = Object.fromEntries(urlSearchParams.entries());
            if (params.id) {
                this.setState({id:params.id})
            }
        });
        this.getNotification();
        Utils.scrollToTop();
 }

My problem in this update is the listen functionality, which existed in react-router-dom v5. Because after any state change in the Child prop, all states cleared, so callBack is always undefined, and I couldn't implement listen functionality in my wrapper. In this part of the code, the callback is always undefined:

 React.useEffect(() => {
      if (callBack) {
        callBack(location,action)
      }
    }, [location])

Does anyone know how to implement my wrapper's listen functionality?

like image 954
Majid M. Avatar asked Jan 22 '26 03:01

Majid M.


1 Answers

The only overt issue I see here are missing useEffect dependencies. callback and action are both missing.

React.useEffect(() => {
  if (callback) {
    callback(location, action);
  }
}, [action, callback, location]);

A much more covert issue is saving a function into React state via the setState updater function. The setState function accepts a value to update the state or a callback function that is passed the previous state value and is expected to return the next state value. If you want to save a callback function into state you'll need to use the callback function syntax and return the callback function you want to save into state.

listen: (callback) => {
  setCallback(() => callback); // not setCallback(callback)
}

Full code:

export const withRouter = Component => props => {
  const [callback, setCallback] = React.useState();
  const location = useLocation();
  const navigate = useNavigate();
  const action = useNavigationType();
  const params = useParams();

  const match = { params };

  const history = {
    push: (url, onMount) => {
      if (onMount) {
        setTimeout(() => navigate(url), 100);
      } else {
        navigate(url);
      }
    },
    listen: (callback) => {
      setCallback(() => callback);
    }
  }

  React.useEffect(() => {
    if (callback) {
      callback(location, action);
    }
  }, [action, callback, location]);

  return <Component {...props} {...{ history, location, match }} />;
};
like image 75
Drew Reese Avatar answered Jan 24 '26 21:01

Drew Reese



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!