import { useMemo } from "react"; | |
import { useParams, useLocation, useHistory, useRouteMatch } from 'react-router-dom'; | |
import queryString from 'query-string'; | |
// Usage | |
function MyComponent(){ | |
// Get the router object | |
const router = useRouter(); | |
// Get value from query string (?postId=123) or route param (/:postId) | |
console.log(router.query.postId); | |
// Get current pathname | |
console.log(router.pathname) | |
// Navigate with with router.push() | |
return ( | |
<button onClick={(e) => router.push('/about')}>About</button> | |
); | |
} | |
// Hook | |
export function useRouter() { | |
const params = useParams(); | |
const location = useLocation(); | |
const history = useHistory(); | |
const match = useRouteMatch(); | |
// Return our custom router object | |
// Memoize so that a new object is only returned if something changes | |
return useMemo(() => { | |
return { | |
// For convenience add push(), replace(), pathname at top level | |
push: history.push, | |
replace: history.replace, | |
pathname: location.pathname, | |
// Merge params and parsed query string into single "query" object | |
// so that they can be used interchangeably. | |
// Example: /:topic?sort=popular -> { topic: "react", sort: "popular" } | |
query: { | |
...queryString.parse(location.search), // Convert string to object | |
...params | |
}, | |
// Include match, location, history objects so we have | |
// access to extra React Router functionality if needed. | |
match, | |
location, | |
history | |
}; | |
}, [params, match, location, history]); | |
} |
@Simply1993 Since this hook calls useLocation
it will cause all components that utilize it to re-render on route change. That can be unnecessary if a component is only importing useRouter
to call router.push
. If those extra re-renders are a performance problem then I'd recommend just using the React Router hooks directly. Let me know if you think something else is going on here though.
I said:
If we leave only history (e.x.), the result is the same.
As far as I know, useMemo does not check equal for deep objects.
So you have to do something like this:
const location = useLocation();
const [locationState, setLocationState] = useState(location);
const history = useHistory();
const [historyState, setHistoryState] = useState(history);
const match = useRouteMatch();
const [matchState, setMatchState] = useState(match);
useEffect(() => {
if (!isEqual(locationState, location)) {
setLocationState(location);
}
}, [location]);
useEffect(() => {
if (!isEqual(historyState, history)) {
setHistoryState(history);
}
}, [history]);
useEffect(() => {
if (!isEqual(matchState, match)) {
setMatchState(match);
}
}, [match]);
return useMemo(() => ({
history: historyState,
match: matchState,
location: locationState,
}), [historyState, matchState, locationState]);
@Simply1993 The result would be the same if you used the useHistory hook directly right? useRouter doesn't cause any re-renders itself because it doesn't set state. To the degree that there are re-renders and new object references are returned, that's a decision made by the underlying React Router hooks (or a bug that should be reported with them).
Cannot find name 'queryString'.ts(2304)
what can i do with this?
react-router-dom is on its version 6, i'd suggest to adapt to this version
react-router-dom is on its new version 6.4, i'd suggest to adapt to this version.
@gragland I updated the hook with the react-router-dom latest version 6.
import { useMemo } from "react";
import {
useParams,
useLocation,
useNavigate,
useSearchParams,
} from "react-router-dom";
// Usage
function MyComponent() {
// Get the router object
const router = useRouter();
// Get value from query string (?postId=123) or route param (/:postId)
console.log(router.query.postId);
// Get current pathname
console.log(router.pathname);
// Navigate with router.navigate()
return <button onClick={(e) => router.navigate("/about")}>About</button>;
}
export function useRouter() {
const params = useParams();
const location = useLocation();
const [searchParams] = useSearchParams();
const navigate = useNavigate();
let queryObj = {};
for (const [key, value] of searchParams.entries()) {
queryObj[key] = value;
}
// Return our custom router object
// Memoize so that a new object is only returned if something changes
return useMemo(() => {
return {
pathname: location.pathname,
// Merge params and parsed query string into single "query" object
// so that they can be used interchangeably.
// Example: /:topic?sort=popular -> { topic: "react", sort: "popular" }
query: {
...params,
...queryObj,
},
// Include location, navigate objects so we have
// access to extra React Router functionality if needed.
location,
navigate,
};
}, [params, location, navigate]);
}
export default MyComponent;
Sure, look.
If we leave only history (e.x.), the result is the same.