paratron / hookrouter Goto Github PK
View Code? Open in Web Editor NEWThe flexible, and fast router for react that is entirely based on hooks
The flexible, and fast router for react that is entirely based on hooks
setQueryParams
would merge rather than replace existing query params:Current:
start URL: /?animal=dog
action: setQueryParams({ name: 'fido' })
end URL: /?name=fido
Proposed:
start URL: /?animal=dog
action: setQueryParams({ name: 'fido' })
end URL: /?animal=dog&name=fido
?
from the URL:Current:
start URL: /
action: setQueryParams({})
end URL: /?
Proposed:
start URL: /
action: setQueryParams({})
end URL: /
It would be great to see the example highlighted in your blog post and documentation in a CodeSandbox. I would be happy to contribute and create one if you don't have time!
According to the documentation:
// These are the same
navigate('/test?a=hello&b=world');
navigate('/test', false, {a: 'hello', b: 'world'});
However the above is not true. First example navigates to '/test' and the second example navigates to '/test?a=hello&b=world'
This is more of a question but what I want to secure a route? F.ex check if the user has a certain claim or something.
Would the following be a good approach:
import React, { useState, useEffect } from 'react';
import {useRoutes, A, navigate } from 'hookrouter';
const HomePage = () => {
return (
<div>
HomePage
</div>
);
};
const AboutPage = () => {
return <div>About</div>
}
const NotFoundPage = () => {
return <div>Not found</div>
}
const UnAuthorized = () => {
return <div>Unauthorized</div>
}
const Secure = ({children}) => {
const isAuthorized = false; // imagine checking an Auth token.
useEffect(() => {
if(!isAuthorized){
navigate(`/unauthorized`)
}
});
return children
}
const routes = {
'/': () => <HomePage />,
'/about': () => <Secure><AboutPage /></Secure>,
'/unauthorized': () => <UnAuthorized />
};
const App = () => {
const routeResult = useRoutes(routes);
return {routeResult || <NotFoundPage />
}
export default App;
Is this approach a good practise? Or is there something better out of the box for this?
Just finishing integrating hookrouter into my current project. I like it a lot so far! Very easy to implement and reason around.
On a certain page my user is filling out a form, and I want to confirm before they can leave, to prevent loss of unsaved data.
I think it would be better if this was baked into the package somehow. I'd want something simple where you could set it for a whole page and it applies to children and parents. I also would prefer to be able to pass any function, in case window.confirm() isn't good enough.
Definitely digging the idea, awesome work! What about the ability to navigate back or even further in history? Or being able to block navigation to eg. show question about saving a form. I've actually made hook version of a Prompt component which turned out to be fairly easy.
function usePrompt(when, message) {
const { history } = React.useContext(Router.__RouterContext);
React.useEffect(() => {
if (when && !context.staticContext) {
return history.block(message)
}
}, [when, message])
}
Would you be interested in history integration of this project or is that out of the scope?
The library is really cool. It doesn't work with server side rendering. Digging through the source code, I see that there are a lot fo window api's being called. I'm not sure how I would get this to work on SSR.
Sometimes it's useful to have an additional state within the navigation intent, but it might be bulky and more structured than what URI is providing.
Line 45 in 9d52bc2
Would be lovely if we could specify state object instead of having a fixed null
there.
Then there is a matter of how to consume it. I think it can be the second argument to the route callback. Some contrived example follows.
const routes = {
'/': () => <HomePage />,
'/products/:id': ({id}, { type }) => <ProductDetails id={id} type={type} />
};
const handleClick = () => {
navigate('/products/12', { type: 'main' });
};
I have a component where you can set one id as active and navigate to it without a url parameter from the nav, but there is an optional url parameter that acts as an override.
As a workaround, when I was converting my application over from React Router, I made two routes, one with the param and one without, but it would be convenient to consolidate with an optional param.
For reference here's the before and after for that component.
// react-router (I made a custom HOC to clean up the API)
export const routes = [{
path: '/',
exact: true,
component: List,
}, {
path: '/create',
component: Create,
}, {
path: 'edit/:id?',
component: Edit,
}, {
path: '/view/:id?',
render(props) {
return <Edit readOnly {...props} />;
},
}];
// hookrouter
const routes = {
'/': () => <List />,
'/create': () => <Create />,
'/edit': () => <Edit />,
'/edit/:id': ({ id }) => <Edit id={id} />,
'/view': () => <Edit readOnly />,
'/view/:id': ({ id }) => <Edit id={id} readOnly />,
};
This is definitely a nice to have, and as you can see from this example it's already cleaner using hookrouter, but if we could accommodate for optional parameters I could get rid of a couple extra lines of code.
I've spoke about this before but still having an issue. Going to try a different approach to explaining it.
function ParentComponent () {
const [value, setValue] = useState(1)
const routeResult = useRoutes({
'/': () => <ChildComponent value={value} setValue={setValue} />
})
return (
<div>
{routeResult}
</div>
)
}
and separately,
function ChildComponent (props) {
function changeToTwo() {
props.setValue(2)
}
return (
<div onClick={changeToTwo}>
{props.value}
</div>
)
}
In this simplified example, the value rendered on screen via ChildComponent does not update from 1 to 2, even though the ParentComponent is aware of the change. Without hook router in the picture, the child component updates because its props do. However with hook router, it does not. Is this intentional or a bug?
Should be chained when multiple interceptors are registered. The evaluation order is LIFO. If an interceptor in the chain returns currentPath
the chain evaluation will stop.
Interceptor functions need a third argument: the parent match path in a nested scenario.
I have a case where I need to know the route I'm on from within a component down the tree. I see that getWorkingPath
is exported in the router but not in the exports from hookrouter. Could we add this to the exports?
In a couple of automated tests, we need to set the current route so it renders what's expected based on the current route.
I suppose it's as easy as exposing a function that modifies closest context value. It could be tricky in regards to nested routing though because tests don't include the whole App, but only a portion of it.
Just an idea, needs some thinking through. What I've always missed with react-router
is some DRYness in route paths and navigation. We have various paths floating all around and it's easy to miss one in case of some change because it's just a string.
For example, when I specify /products/:id
in routes, it would be great to somehow get a function bound to such route. Instead of repeating that string, I would like to call something like navigateProducts({ id: 11 })
.
Perhaps it's more of a job for some code generator tool than a runtime solution. Mainly because it would be lovely to have types for such calls (sorry @Paratron).
The docs need to mention that route result functions need to be pure.
The docs also need to advise that route objects should be defined outside the component who uses the route hook so route objects won't get destroyed and renewed on every re-render.
By making the customPath
be a singleton in module scope, SSR can not be asynchronous and detecting redirects is harder.
I think it would be prudent to add the path to the Context and inject it at the top of the tree when doing SSR. That way each tree has its own private path and keeping track of redirects is easy
Component libraries (material ui for example) and people most likely want/require to reference the native element rather than the wrapper component
Should be changeable without any breaking changes probably (but check out: https://reactjs.org/docs/forwarding-refs.html#note-for-component-library-maintainers)
Is there a way to support URL hash change instead of route change like React Router? I have a need to use hash instead of path for routing because I'm building an electron application which is served at /index.html
and isn't served by a web server, so route change doesn't work properly. Is this possible to support as a configuration?
use cases:
I'm trying to apply useControlledInterceptor
to manage an unmount navigation on a generic component.
const [className, setClassName] = useState(classes.incoming);
const [nextPath, confirmNavigation] = useControlledInterceptor();
useEffect(() => {
if (!nextPath) {
return;
}
setClassName(classes.out);
setTimeout(confirmNavigation, 200);
}, [nextPath]);
Outside of this component I have <A href="/">...</A>
, and when I click this link (or perform any other behavior in the app that causes navigation) I get this url in my address bar http://localhost:3000/function%20(nextPath,%20currentPath)%20%7B%20%20%20%20%20%20setInterceptedPath(nextPath);%20%20%20%20%20%20return%20currentPath;%20%20%20%20%7D
and my 404 page displays.
I assume that this is because the interceptors don't link in with the router context so it only works with navigation caused from within the same component, but I haven't dug into it too far yet.
Right now, useRoutes()
consumes a route object on first call, then ignores subsequently passed objects for better caching.
There should at least be a shallow comparsion if a new routes object has been passed since available routes may change with application state.
There is one routing path in my application that hits the throw 'wth';
line that runs when stackEntry
is falsy. Outside of my routing logic I render a navigation menu with <A href="/" />
. There's only one page where clicking that link causes it to throw this error. It doesn't break anything, but I thought I'd mention it since it's kind of an interesting case. Also because it's just a beautiful error.
When it happens, I see
> JSON.stringify(stack, null, 2)
"{
"1": {
"routerId": 1,
"originalRouteObj": {},
"routes": [
[
"/",
null
],
[
"/campaigns*",
null
],
[
"/utilities*",
null
],
[
"/characters*",
null
]
],
"parentRouterId": null,
"matchedRoute": "/",
"reducedPath": "",
"passContext": false,
"result": {
"key": null,
"ref": null,
"props": {},
"_owner": null,
"_store": {}
}
}
}"
> parentRouterId
8
Awesome library, really loving the different appraoch based on hooks!
I've ran into an issue where the usePath state doesn't seem to get updated when navigating with/through browser history.
Reproduction on a bare bones CRA setup: https://codesandbox.io/s/usepathonpopstate-bug-el812
As far as i can tell, the updatePathHooks
function just doesn't get called at the end of the popstate
event listener.
I need to create proper documentation - at best using a tool like docusaurus.
I am asking because I don't see any open/closed tickets for this, and I suspect other people would want to know too.
Is Native planned to be supported? How hard would it be to support Native?
In one router there were feature to set each route an ID, so later you can use <A to="some-id">Go to ID</A>
, So, if topology of routes changed, you no need to change links all over the code.
Every time I started new project, thinking about this great feature. What do you think?
The current approach is to use A
to do this directly.
import { A } from 'hookrouter';
export const Link = () => (
<A href="/">Root link</A>
);
This works great until you have a framework that wants to be given href
/ onClick
properties for navigation. It would be nice if the internals of A
were exported separately so that they could be reused, something like:
export const getLinkProperties = (props) => {
const onClick = (e) => {
e.preventDefault();
navigate(e.currentTarget.href);
if (props.onClick) {
props.onClick(e);
}
};
const href = props.href.substr(0, 1) === '/'
? getBasepath() + props.href : props.href;
return { href, onClick };
}
Then callers would be free to use it as:
<MyLink whatever="x" {...getLinkProperties({ href: '/' })}>
My link's text
</MyLink>
Then A
becomes a simple class wrapper:
const A = (props) => (
<a {...props} {...getLinkProperties(props)} />
);
There is a long-standing discussion in react-router
about this. I haven't read a whole thing, it's back and forth in there. So I am wondering if some thinking out of the box can be applied to the hooks solution.
The basic idea is that you can call eg. navigate('../')
to simply move higher in a hierarchy relative to a current path.
Relative routes are a different story and I wouldn't worry about that right now.
Nice and clean. Do you plan to have a way of handing query string?
Current implementation of query parsing is verry limited and does not suport query sorting or even arrays. I would like to use query-string for query parsing.
While writing up a standard 404 page, I noticed an interesting behavior where if I navigate to a 404 page, it works as you would expect. However, if you try to navigate in-app from there to another page that also triggers the 404 route, then nothing is triggered.
This causes confusing state, especially if you use window.location.pathname
in the 404 component.
Fortunately this is not a very realistic scenario in most webapps, but it's very easy to notice while building out a new one.
I see that navigate
will always push, but sometimes it's required to navigate by replacing.
For example route /product/new
, when the form is saved, I would like to replace history with eg. /product/15
and then navigate with a push to /products
. In case the user goes back, he should go to the created product page and not to create a new one again.
To make this happen it's either about introducing navigateReplace
as a separate function or perhaps wrapping the second argument of navigate
to some options. Honestly, I don't think that query params are that common to have such a prominent place there.
navigate('path', { replace: true, queryParams: {...}})
Currently if you will click on link with Ctrl pressed new tab won't be open because default behavior is prevented.
I'm trying to think how waiting for a route to be fully loaded before mounting could be implemented.
Any thoughts?
Nice library. I have migrated to this from react-router. One issue I encountered is there is no analogy of react-router Redirect component. This component replaces the current item in history by the provided one and navigates the page to the provided path.
import React from 'react';
import { navigate } from 'hookrouter';
export const Redirect = (props: { href: string }) => {
navigate(props.href, true);
return <></>;
};
I definitely need to create tests before this can be moved out of beta. I used it in a couple of customer projects right now and it seems to work fine, but tests are mandatory ๐ช
Could you pls showcase example how to rerender screen when user navigates browser history? Would be really nice use-case coverage. For now browser history does not seem to affect hookrouter.
@Paratron I assume you are not TypeScript user, but many TS users would appreciate having types available.
The best course of action is, of course, have the source in TypeScript. It can compile to clean JS a similar way as Babel can. Or you can even use Babel to compile and TypeScript to extract typings only.
Otherwise, it means to manually synchronize types for every API change.
I can help with either way.
It's not healthy for the Router market to be so dominated by the React Router. I want to level the playing field so that your Hook Router gets a fair crack of the whip. That's why I've written the Router Challenge. It aims to do for Routers what TodoMVC did for UI frameworks by offering the same SPA built in React using different Routers. For it to be successful I need your help. Will you take the Router Challenge and implement the SPA using your Hook Router, please?
Lines 204 to 209 in c1871c3
at useRoute(routeObj)
internal id created by random function.
I think it is good to use https://github.com/puleos/object-hash on routeObj.
This will guarantees the uniqueness of the id of each route object.
It would be lovely to have more power in the routing path. Mainly I'm thinking regex, to acheive effects similar to express-router.
Here's a few examples:
const routes = {
'/': () => <HomePage />,
'/ab+cd': () => <Alphabet />,
'/ab(cd)?e': () => <ProductOverview path={$1} />,
/a/: () => <ProductDetails id={$1} />
};
I have conditional rendering case like
props => {
let match = useRouter({
'/sign-in': () => <SignInScreen/>,
'/dashboard': () => <DashboardScreen/>
})
return <main>
{isSignInScreen && <NavBar/>}
{match || <>Not Fount</>}
</main>
}
What's the best way to detect isSignInScreen
flag?
How difficult would it be to implement a wildcard redirect?
So instead of having to write 6 redirects, it would be similar to this:
function Component() {
useRedirect('*', '/login')
//...
}
Some problems I can think of is that this might overwrite routing that is already mapped, so some route checking will be required.
Is there a way to navigate back one place in the browser's history?
It's a bit unrealistic to refactor a bigger application routing at once. It is partially related to a propose static route #15 for testing. It would make sense if we could set an path prefix to specify where is that routing table sitting right now. Something like this.
const routeResult = useRoutes({
'/category': () => <MenuCategoryListPage />
}, '/settings/menu')
I made myself a simple function that adds that prefix to those routes. Only later I've realized it's not enough because navigate
and useRedirect
won't take that into account. It needs to be in the context to be really useful.
This is more of a suggestion, but it would be helpful if you created git tags to mark the npm
release versions of code that you release in the future.
Picking up where convo started to get off topic in #27
I tried/failed to understand this pattern. I think what's happening is the route triggers a function which returns another function which then returns a component with new props. If that's correct, I think I got lost by not understanding where the variable 'product' was coming from.
Anyway, I think I've solved my original issue (routeResult
rendered child component not updating) by adding the menu state as a new route. So now it's /product/menu
when the menu is open and closing it triggers plain old /product
. The downside is some potentially counter-intuitive things with the browser's back button, but passing true as second argument to navigate()
I think has smoothed that out.
Feel free to just close this issue if there's nothing more specific to hook router to say on the matter :)
When I use the <A>
tag it binds a handler that does a noop, it doesn't redirect anywhere.
queryParams uses a singleton to manage listeners, so this will easily leak if not unlistened, and it will cause query params of two different in-progress SSR runs to be mixed up
(also, the URLSearchParams it uses is not supported in IE at all, probably worth documenting that it needs a polyfill there)
This is an example of my router code, which is pretty basic:
// If no route is found, always send them to UnknownPage
return useRoutes(routes) || <UnknownPage />;
The routes
object is currently rather trivial. The problem occurs with the new usePath
hook used within the UnknownPage
component.
Within the render
method of that component I fetch and display the path
:
// UnknownPage
render() {
const path = usePath();
// rendered a little prettier, but logically:
return (
<>404 Unknown page: {path}</>
);
}
This triggers this error:
Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app
See https://fb.me/react-invalid-hook-call for tips about how to debug and fix this problem.
Of interest, if I move the usePath
hook usage outside of the useRoutes
code path, then everything is happy, but it also means every sub-component is getting rerendered unnecessarily.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.