-
-
Save gragland/8322804ba43392d5a1e96d37d1a38218 to your computer and use it in GitHub Desktop.
| 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]); | |
| } |
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;
@Simply1993 Since this hook calls
useLocationit will cause all components that utilize it to re-render on route change. That can be unnecessary if a component is only importinguseRouterto callrouter.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.