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?
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 }} />;
};