Hi guys,
at current React Conf Hooks have been announced for React v16.7. What's the plan for supporting them? Will there be a useRouter()
any time soon?
https://reactjs.org/docs/hooks-faq.html#what-do-hooks-mean-for-popular-apis-like-redux-connect-and-react-router
While I don't know any details about what Michael is planning, _when_ React Router supports them largely depends on the timeline of how fast React 16.7 is released.
React Router currently has its 4.4
beta out, which will probably become final sooner than 16.7 becomes final. In that case, hooks would be a 4.5
feature. RR can put out an alpha/beta build of 4.5
, but it shouldn't become "official" until hooks get a final release.
It looks like __RouterContext
is exposed in v4.4.0-beta.5
- so you could probably just do something like:
import {useContext} from "react"
import {__RouterContext} from "react-router"
export default function useRouter() {
return useContext(__RouterContext)
}
Might be a horrible idea though. 🤷♂️
@third774 that will work for playing around and should essentially be what React Router eventually exports, but the double underscore is intended to mean that the context is private and might change names/disappear (only the context consumer actually needs to be exported).
Yes, we don't intend to make our context instance public. You should use our public APIs to access it, which allows us to warn you when it's used incorrectly and things like that.
You could also create your own wrapper to get access:
import React from "react";
import { BrowserRouter, Route } from "react-router-dom";
export const RouterContext = React.createContext({});
const CustomBrowserRouter = ({ children }) => (
<BrowserRouter>
<Route>
{(routeProps) => (
<RouterContext.Provider value={routeProps}>
{children}
</RouterContext.Provider>
)}
</Route>
</BrowserRouter>
);
export default CustomBrowserRouter;
Then wrap this around your App instead of the normal <BrowserRouter />
import React from "react";
import CustomBrowserRouter from "./CustomBrowserRouter";
function App() {
return (
<CustomBrowserRouter>
<div className="App">
{/* The rest of your app */}
</div>
</CustomBrowserRouter>
);
}
export default App;
Then you can make a useRouter hook:
import {useContext} from "react"
import {RouterContext} from "./CustomBrowserRouter"
export default function useRouter() {
return useContext(RouterContext)
}
@timdorr But in that case, you need to export custom hook that would wrap around the context because useContext(Consumer)
is not supported way.
@FredyC RR can export a hook (useRouter
) to provide its context data without the context being public. I only mentioned the consumer to note that the currently exported context can theoretically be removed, which would break the code of anyone relying on it.
Ya, I think a useRouter
hook makes a lot of sense. It'll probably work just like a path-less <Route>
currently does, and just pass along the current match
object at that point in the tree.
We won't be able to include this feature in the 4.x branch though without shipping some kind of hooks polyfill, which I'd rather not do. We should be able to safely assume that anybody using hooks is also using React 16.7+ which will be our dependency in version 5.
@mjackson can't you do something like
import React, { useContext } from 'react';
export function useRouter () {
if (!useContext) {
throw new Error('React >= 16.7 required');
}
return useContext(...);
}
No polyfill required, you just can't use useRouter
unless you're on 16.7
or higher. This means it can go on a react-router
minor version.
@Janpot I think you're right, maybe we can ship a useRouter
in 4.x with the caveat that you have to be running 16.7+ in order to use it. Not sure why I didn't consider that before, thank you :)
You want to work up a PR?
import { useContext }from 'react'
import { RouteComponentProps, StaticContext } from 'react-router';
import * as H from 'history';
//@ts-ignore
import { __RouterContext as RouterContext } from 'react-router'
// FIXME: use official API when https://github.com/ReactTraining/react-router/pull/6453 merged
export function useRouter<Params extends { [K in keyof Params]?: string } = {}, C extends StaticContext = StaticContext, S = H.LocationState>() {
return useContext(RouterContext) as RouteComponentProps<Params,C,S>
}
@xiaoxiangmoe We don't support that code and it will probably break on some arbitrary release at some point in the future. Fair warning :)
@mjackson I am doing the same since you don't want to release version with unstable_useRouter
for whatever reasons :) It's too good to live without that anymore :)
I also use the same workaround with @xiaoxiangmoe. Of course it's only temporary, as noted in the snippet, will replace it with unstable_useRouter
, and eventually, hopefully useRouter
as the official way.
Used the same workaround initially since unstable_useRouter
still has yet to be released, but then I found https://github.com/CharlesStover/use-react-router which does the workaround for you, allowing const {history, match, location} = useReactRouter()
. Figured I should post it here in case it might help anyone because I only found it a few days after implementing the workaround manually (surprised no one else has linked it!)
It does implement pub-sub, which may be different from expected behavior; the implementation details are described in https://medium.com/@Charles_Stover/how-to-convert-withrouter-to-a-react-hook-19bb02a29ed6
Not sure if anyone run into the same issue but when I run it in a browser, useContext(__RouterContext)
returns { match, location, history }
objects correctly, but when I'm running my tests and mocking my router, let's say <MemoryRouter initialEntries={['/something/here']}>
then location
and history
object are correct but match
is always { path: '/', url: '/', params: {}, isExact: false }
. It doesn't represent what it should be.
@mateusznowosielski Shouldn't you also specify initialIndex
? Either way, you can try <Route>{({ match }) => ...</Route>
just to see if it's working. I don't think it's a hook related.
@FredyC initialIndex
wasn't necessary when I had only 1 initialEntry
, but I had to wrap my component within <Route component={ui} path={path} />
. Thanks for the suggestion.
Instead of a useRouter
hook, could we consider a useRoute
instead? I've always been bothered that withRouter
doesn't allow passing in a path.
Would anyone be opposed to useRoute
like so:
import { useRoute } from 'react-router';
export default function SomeComponent() {
const { match } = useRoute({ path: '/:something/:to/:match' });
}
The idea is that the useRoute
would take in all the props a <Route />
would take in and pass them to the second argument of matchPath
.
@mjackson, what do you think?
I'd be willing to work on a PR if I get your blessing too. Let me know! Oops. I didn't realize that your existing PR already has the capability.
@ricokahler There is already a PR that does that. It just needs a little update as @mjackson managed to stall it until hooks became an official 🙊
@FredyC I did not realize the PR already did this! I think I just assumed since withRouter
didn't take any props that useRouter
wouldn't either.
@mjackson Forget I said anything and release this ha! 🎉
Hello dear people - how is the "useRouter" situation?
exporting react-router context api would make it easy to user build their own hooks on top of it
so, we don't need to wait until the final hook solution lands
@sibelius There is a context exported in 4.4 beta ... https://github.com/ReactTraining/react-router/issues/6430#issuecomment-439056692
Here's what I had with typescript if it helps anyone viewing this thread:
First added some module augmentation to expose the __RouterContext
as a type.
// types/react-router-dom.d.ts
import { RouteComponentProps } from 'react-router-dom';
import { Context } from 'react';
declare module 'react-router-dom' {
export const __RouterContext: Context<RouteComponentProps<{}>>;
}
Then I made a custom hook around that.
import { useContext } from 'react';
import { __RouterContext, RouteComponentProps } from 'react-router-dom';
export default function useRouter<TParams = {}>() {
return useContext(__RouterContext) as RouteComponentProps<TParams>;
}
And in use it would be like:
const { match: { params: { id } } } = useRouter<{ id: string }>();
Hi, do we still not have any implementation of hooks merged yet as of 5.0.0?
@ms-ww 5.0 wasn't actually a feature release, just a synchronization of versions.
Looking at withRouter and stripping out the HOC stuff, it seems like a useRouter implementation would just be:
import { useContext } from 'react';
import RouterContext from './RouterContext';
import invariant from 'tiny-invariant';
/**
* A public hook to access the imperative API
*/
export default function useRouter() {
invariant(useContext, 'React >= 16.8 required');
const context = useContext(RouterContext);
invariant(context, `You should not use useRouter outside a <Router>`);
return context;
}
@dantman That's basically what this library is doing, but it has an added forceUpdate
effect in it to make sure your buried component re-renders.
You could also create your own wrapper to get access:
import React from "react"; import { BrowserRouter, Route } from "react-router-dom"; export const RouterContext = React.createContext({}); const CustomBrowserRouter = ({ children }) => ( <BrowserRouter> <Route> {(routeProps) => ( <RouterContext.Provider value={routeProps}> {children} </RouterContext.Provider> )} </Route> </BrowserRouter> ); export default CustomBrowserRouter;
Then wrap this around your App instead of the normal
<BrowserRouter />
import React from "react"; import CustomBrowserRouter from "./CustomBrowserRouter"; function App() { return ( <CustomBrowserRouter> <div className="App"> {/* The rest of your app */} </div> </CustomBrowserRouter> ); } export default App;
Then you can make a useRouter hook:
import {useContext} from "react" import {RouterContext} from "./CustomBrowserRouter" export default function useRouter() { return useContext(RouterContext) }
This is a poor conclusion, it doesn't help anyone willing to learn from your explanation. "Then you can make a useRouter hook:" what is the purpose? Where is it used? What does it do?
I implemented a file called routerHooks.ts
that looks like:
import { __RouterContext, RouteComponentProps } from 'react-router';
import { useContext } from 'react';
// The react-router guys say that this will be what the official API probably looks like
// https://github.com/ReactTraining/react-router/pull/6453#issuecomment-474600561
export const useLocation = () => {
const context = useContext(__RouterContext);
return context.location;
};
export function useMatch<P = {}>() {
const context = useContext(__RouterContext) as RouteComponentProps<P>;
return context.match;
}
export function useParams<P = {}>() {
const context = useContext(__RouterContext) as RouteComponentProps<P>;
return context.match.params;
}
Our team has a set of more powerful router related hooks works fine in tens of projects:
import {useContext, useMemo, useCallback} from 'react';
import {__RouterContext as RouterContext, RouteComponentProps} from 'react-router';
import {Location} from 'history';
import * as queryString from 'query-string';
import uriTemplate, {URITemplate} from 'uri-templates';
type ParsedQuery = queryString.ParsedQuery;
export const useRouter = <T>(): RouteComponentProps<T> => useContext(RouterContext) as RouteComponentProps<T>;
export const useLocation = (): Location => {
const {location} = useRouter();
return location;
};
export const useParams = <T>(): T => {
const {match} = useRouter<T>();
return match.params;
};
export const useQuery = <T extends ParsedQuery>(): T => {
const {search} = useLocation();
const query = useMemo(
() => queryString.parse(search),
[search]
);
return query as T;
};
interface UpdateQueryOptions {
replace: boolean;
}
type UpdateQuery<T> = (patch: Partial<T>) => void;
type Visit<T> = (params: T) => void;
const USE_PUSH = {replace: false};
export const useUpdateQuery = <T extends ParsedQuery>(options: UpdateQueryOptions = USE_PUSH): UpdateQuery<T> => {
const {history} = useRouter();
const query = useQuery<T>();
const {replace} = options;
const updateQuery = useCallback(
(patch: Partial<T>): void => {
const newQuery = {...query, ...patch};
const newSearch = queryString.stringify(newQuery);
if (replace) {
history.replace({search: newSearch});
}
else {
history.push({search: newSearch});
}
},
[history, query, replace]
);
return updateQuery;
};
export const useNavigate = <T>(to: string | URITemplate, options: UpdateQueryOptions = USE_PUSH): Visit<T> => {
const {history} = useRouter();
const {replace} = options;
const template = useMemo(
() => {
if (typeof to === 'string') {
return uriTemplate(to);
}
return to;
},
[to]
);
const visit = useCallback(
(params: T): void => {
const newLocation = template.fill(params as any);
if (replace) {
history.replace(newLocation);
}
else {
history.push(newLocation);
}
},
[template, history, replace]
);
return visit;
};
@otakustay what version of react-router are u using? I just installed 5.0 and I can't find __RouterContext
I guess this idea works if you are using <= 4.3.1, but, it would be better to wait for this https://github.com/ReactTraining/react-router/pull/6453#issuecomment-474600561 because if something changes (https://github.com/ReactTraining/react-router/pull/6753) then your code might break
Our team has a set of more powerful router related hooks works fine in tens of projects:
I've been using this code successfully for a few months now:
import * as React from "react";
import { BrowserRouter, Route } from "react-router-dom";
const RouterContext = React.createContext(null);
export const HookedBrowserRouter = ({ children }) => (
<BrowserRouter>
<Route>
{(routeProps) => (
<RouterContext.Provider value={routeProps}>
{children}
</RouterContext.Provider>
)}
</Route>
</BrowserRouter>
);
export function useRouter() {
return React.useContext(RouterContext);
}
But either way, I don't understand why this issue is still open? Is there a complication to adding any of these variations of useRouter code?
I also don't understand why there is still no useRouter
hook that comes with react-router
OOTB
I moved from react-router to https://www.npmjs.com/package/hookrouter and very happy with it. Code becomes a lot simpler, easier to understand, shorter, size bundle is also smaller.
@avkonst how does that handle nested routes?
Seems all right. There are docs with examples on nested routes. The only
component I added on top of hookrouter is Redirect (3 lines of code). This
allowed to replace react router completely.
On Thu, 27 Jun 2019, 12:46 japrogramer, notifications@github.com wrote:
@avkonst https://github.com/avkonst how does that handle nested routes?
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/ReactTraining/react-router/issues/6430?email_source=notifications&email_token=AA6JSVMJLVELCDJ44WOTL63P4QEW5A5CNFSM4F7XI2V2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGODYVGMUA#issuecomment-506095184,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AA6JSVM6JHM37IT6ALVJ6U3P4QEW5ANCNFSM4F7XI2VQ
.
@maxparelius : Shipping hooks would have meant dropping support for older react versions. That's quite a big breaking change. We'll probably get there in 6.0, but that's apparently quite the big release with more than just hooks. So you have to be patient, I'm afraid.
(And remember that 5.0 wasn't intended as a major release, so no intentional breaking changes like this made it in)
@StringEpsilon Definitely, but we could introduce a Router hook with a version switch. What I mean, is, that for React versions lower than the needed version there will be an error, that this React version is not supported. For other React versions it should just work. This would be a compromise.
That seems reasonable. Why aren't we doing that?
Good question. I will push a proposal later that day.
@StringEpsilon Here we go!
I'm using react-router 5.0.1 here, does it mean I have the useRouter hook? Why this issue is even open if it's true?
@jayarjo Because there is no useRouter()
hook right now. My draft was rejected, because currently there seems to be no clear direction about how this hook should work and what it should return. There is also no clear opinion if there will be this hook at all or only hooks returning values from the context (e.g. params, location, history).
We're not making any progress here, so I'm going to close this. We will have a few hooks in the next major release, maybe even in a minor release. If you absolutely cannot wait, please be my guest and use some other router that ships with hooks.
@mjackson definitely understand that settling on hook API/behavior will take time and patience. However, can we keep this or a related issue open so folks can track when hooks have been added to the library? I often use Github's "closed" PR notifications to know when features are available, especially features that don't have a specific release designation.
@MeiKatz submitted a PR for hooks that I found helpful, so I copied the gist below. It wasn't merged due to some uncertainty about the api, but these will work for me until react-router
gets official hooks.
Caveat: it uses __RouterContext
, which is a private api, so these hooks are unsupported.
// react-router-hooks.js
import { useContext } from 'react';
import { __RouterContext as RouterContext } from 'react-router';
export function useRouter() {
return useContext(RouterContext);
};
export function useParams() {
const { match } = useRouter();
return match.params;
}
export function useLocation() {
const { location, history } = useRouter();
function navigate(to, { replace = false } = {}) {
if (replace) {
history.replace(to);
} else {
history.push(to);
}
}
return {
location,
navigate
};
}
@sdegutis do you have it as a repo somewhere?
I solved it by creating a withUseRouteHandler HOC that passes useRouteHandler as a prop to the wrapped component. useRouteHandler returns a function that takes additional props.
export default function withUseRouteHandler(WrappedComponent) {
return withRouter(({ history, location, match, ...props }) => (
<WrappedComponent
{...props}
useRouteHandler={handler => {
return handler({ match, location, history });
}}
/>
));
};
Did someone try this one?
https://github.com/CharlesStover/use-react-router
Did someone try this one?
https://github.com/CharlesStover/use-react-router
I have. It worked fine as long as you're not too fussy on unit testing - we struggled to get it to play ball with RTL / renderHooks
I mean, why _is_ the context private? If that were exposed, functional components could use useContext
, class components could use static contextType
, and hooks could be implemented by the community easily using useContext
.
I believe it is private because it is likely to change
I believe it is private because it is likely to change
That would make sense if withRouter
didn't already expose it.
Exactly. And the last time the context's interface changed, withRouter
wasn't modified so end-user code wouldn't have to be updated. So we already have the precedent that "context interface changes -> end user code changes". Which would be the same if the context was directly exposed.
To be honest, the router context is exposed, but only in a way that tells the people, "watch out, maybe we will rename it in the future, so your own code might not work in the future". You can easily import it via
import { __RouterContext as RouterContext } from "react-router";
I've been using this code successfully for a few months now:
import * as React from "react"; import { BrowserRouter, Route } from "react-router-dom"; const RouterContext = React.createContext(null); export const HookedBrowserRouter = ({ children }) => ( <BrowserRouter> <Route> {(routeProps) => ( <RouterContext.Provider value={routeProps}> {children} </RouterContext.Provider> )} </Route> </BrowserRouter> ); export function useRouter() { return React.useContext(RouterContext); }
But either way, I don't understand why this issue is still open? Is there a complication to adding any of these variations of useRouter code?
Thanks for the solution! Easy and simple. I hope we will soon have this Hook. It'll be incredibly handy.
podrias poner algo asi en tu
"react-router-dom": "latest",
package.json y luego yarn install o npm i
Is there a discussion somewhere about the decision to expose 3 separate hooks, rather than a single useRouter
hook?
If I'm understanding this correctly, the current hooks api will require reading the router context twice (for cases where you need at least 2 in the set of location
, match
or history
), whereas a single useRouter
hook would've afforded us the opportunity to just consume the context in one place.
Found this comment from @mjackson in a related PR:
Just for posterity, we released some hooks in React Router 5.1
https://github.com/ReactTraining/react-router/releases/tag/v5.1.0
Most helpful comment
You could also create your own wrapper to get access:
Then wrap this around your App instead of the normal
<BrowserRouter />
Then you can make a useRouter hook: