Skip to content

Instantly share code, notes, and snippets.

@gragland
Last active December 27, 2022 07:36
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save gragland/8322804ba43392d5a1e96d37d1a38218 to your computer and use it in GitHub Desktop.
React Hook recipe from https://usehooks.com
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
Copy link

Simply1993 commented Sep 4, 2020

Sure, look.
Снимок экрана 2020-09-04 в 10 30 59

If we leave only history (e.x.), the result is the same.

@gragland
Copy link
Author

gragland commented Sep 9, 2020

@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.

@Simply1993
Copy link

Simply1993 commented Sep 9, 2020

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]);

@gragland
Copy link
Author

gragland commented Sep 9, 2020

@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).

@Ridnois
Copy link

Ridnois commented Oct 23, 2020

Cannot find name 'queryString'.ts(2304) what can i do with this?

@esau-morais
Copy link

esau-morais commented Sep 17, 2022

react-router-dom is on its version 6, i'd suggest to adapt to this version

@BrunoTeixeiraNDB
Copy link

react-router-dom is on its new version 6.4, i'd suggest to adapt to this version.

@phanendraguptha
Copy link

@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;

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment