-
Notifications
You must be signed in to change notification settings - Fork 34
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Router #244
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Amazing job guys 😍
linkProps.className += ` ${activeClassName}`; | ||
} | ||
|
||
if (typeof type === 'undefined') { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what happened if type
is equal to null
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
type
prop is meant to contain type
property for a button
element. if somebody passes a type
as a prop, then a <button>
gets rendered, otherwise an <a>
. I'm not even sure what should happen when somebody passes null
there and what the developer expects in that case.
const linkContent = <span className="decorated">About</span>; | ||
|
||
const wrapper = shallow( | ||
<Link className="fancy-button" to="/about" type="button">{linkContent}</Link>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fancy-button
❤️
path: PropTypes.string, | ||
exact: PropTypes.bool, | ||
computedMatch: PropTypes.object, | ||
component: PropTypes.oneOfType([PropTypes.node, PropTypes.func]), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't we accept PropTypes.element
as well here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
here we expect a definition of component, a class, not an instance (Component
, not <Component />
). that's why PropTypes.element
doesn't fit here. so it's a function, because JavaScript :). also there can be stateless components, those are definitely functions.
actually PropTypes.node
might not be needed. I am going to check.
|
||
const RouteApp = nextProps.app; | ||
|
||
this._appInstance = new RouteApp({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Couldn't we get
the instance instead of creating a new object ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we can't get
because the instance does not live anywhere. these are App classes that are passed to <Route app={App} />
components.
since they are not registered to the Root app, like how we do it for Apps targeting Regions, they can't be obtained later from somewhere else. these Apps' lifecycle are contained by the <Route>
.
const matched = this.props.computedMatch || this.state.matched || null; | ||
|
||
return ComponentToRender !== null && matched !== null | ||
? <ComponentToRender match={matched} /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
just a matter of style but maybe a if
here could be more readable
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
both look fine to me. I wouldn't change it unless many people find it unreadable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I also think that both provide the same level of readability, therefore would keep it the way it is.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah that's why I said a matter of style xD
|
||
React.Children.forEach(this.props.children, (element) => { | ||
if (child !== null) { | ||
return; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't we explicitly return null
here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think so. Here it's works as a continue
statement for .forEach
loop.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh yeah I didn't see it sorry
export default function matchFromHistory(pattern, history, options = {}) { | ||
if (!pattern) { | ||
return { | ||
url: history.location.pathname, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wouldn't it be better to check if history.location
exists in the if
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think history.location
will always exist. At least it was always the case in unit tests with MemoryRouterService
even before pushing any URLs in it. The idea here is to return any match if there's no pattern to match. @fahad19, am I correct?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yep.
|
||
const matched = re.exec(history.location.pathname); | ||
|
||
if (!matched) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would check as well Array.isArray(matched)
<div> | ||
<Route path="/" component={HomePage} exact /> | ||
<Route path="/about" component={AboutPage} /> | ||
</div> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just to make sure I understand: so this piece of code won't render any actual content, it's just for registering routes to components? Or will this actually display something?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it won't display until window url match the path
Really simplified version of this would be a function:
function Route({path, component}){
if (window.location.path === path) {
return <component match={someDataAboutCurrentRouteLikeParams} />
}
return null;
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it will render, once the current location in the browser matches to any of the paths. if there is a match, then the assigned component (HomePage
or AboutPage
) will render accodingly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah ok, I think I understand, so the difference between this and using <Switch>
is that this would simply render all the components which match, not just the first match, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
exactly.
<Switch>
would always render the first matching child, and stop there. If nothing else matches, then the last child (if it happens to have no path
prop) will be rendered.
Much like switch
in the language, with default
as the last case.
handleClick = (e) => { | ||
e.preventDefault(); | ||
|
||
// TODO avoid pushing route if it's current URL |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How big is this task, can we do it now? 😺
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@viacheslaff: could you kindly take a look at this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've added this TODO, it's the history
library that issues a warning whenever the same route is pushed. there's a bit of discussion in here about whether it should warn or stay silent: remix-run/react-router#4467. but I guess <Link>
shouldn't push the url if it's path
exactly matches current url
linkProps.href = to; | ||
|
||
return ( | ||
<a {...linkProps}> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
wow, this is cool, haven't seen the spread being used like this before
packages/frint-router/README.md
Outdated
1. `pattern` (`String`): Pattern to match against | ||
1. `options` (`Object`) | ||
* `options.exact` (`Boolean`): Matches pattern exactly, defaults to `false`. | ||
* `options.cache` (`Boolean`): Cache matches (TODO) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
By TODO
you mean this is not implemented yet? Shouldn't we just remove this from the readme?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
a minimal amount of caching should be supported. repeated route matching can be costly on component re-renders. I will take care of it.
|
||
- `BrowserRouterService`: uses modern HTML5 History API | ||
- `HashRouterService`: for legacy browsers | ||
- `MemoryRouterService`: useful for tests |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Which one should an average app developer use?
Isn't there a way to automatically figure out which one is the best choice with feature detection or something?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it's a bit difficult to decide which one should be used by default by anyone. if your application is backed by a server, then BrowserRouterService
makes sense.
BrowserRouterService
would keep URLs in your browser like /about/page/here
.
But HashRouterService
can work anywhere, as long as the target page is loaded, and URLs would look like this in the browser /index.html#/about/page/here
.
And MemoryRouterService
has nothing to do with the browser's API. it just imitates the behaviour of the other services and very useful in unit testing your app.
We want the developers to pick a service of their own needs when constructing the App, otherwise we end up having too much code in our bundles, that's never executed.
The developer needs to make a conscious decision about it. If you want feature detection, you have to do it yourself when returning the instance of the right service when your router
provider is computed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Might make sense to copy this over into the docs ;)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jvantillo: good idea. I have copied the text, and will send a PR about how to choose the right router service in your app soon. but really want to release it first :D
also plan to revamp the homepage with more content to deliver a better message to developers. a brainstorm meeting invitation is coming soon.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks awesome, a huge amount of work, and really nice, polished docs and sample! 👍
packages/frint-react/package.json
Outdated
"react-test-renderer": "^15.5.4" | ||
"react-test-renderer": "^15.5.4", | ||
"frint": "^2.0.1", | ||
"frint-test-utils": "^2.0.1" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
frint
and frint-test-utils
are duplicates. one pair should be removed
packages/frint-react/package.json
Outdated
@@ -38,7 +38,9 @@ | |||
"prop-types": "^15.5.10", | |||
"react": "^15.5.4", | |||
"react-dom": "^15.5.4", | |||
"react-test-renderer": "^15.5.4" | |||
"react-test-renderer": "^15.5.4", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
because enzyme
, prop-types
, react
, react-dom
, react-test-renderer
are now in root package.json
, they can be removed this package.json
linkProps.className += ` ${activeClassName}`; | ||
} | ||
|
||
if (typeof type === 'undefined') { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
type
prop is meant to contain type
property for a button
element. if somebody passes a type
as a prop, then a <button>
gets rendered, otherwise an <a>
. I'm not even sure what should happen when somebody passes null
there and what the developer expects in that case.
path: PropTypes.string, | ||
exact: PropTypes.bool, | ||
computedMatch: PropTypes.object, | ||
component: PropTypes.oneOfType([PropTypes.node, PropTypes.func]), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
here we expect a definition of component, a class, not an instance (Component
, not <Component />
). that's why PropTypes.element
doesn't fit here. so it's a function, because JavaScript :). also there can be stateless components, those are definitely functions.
actually PropTypes.node
might not be needed. I am going to check.
path: PropTypes.string, | ||
exact: PropTypes.bool, | ||
computedMatch: PropTypes.object, | ||
component: PropTypes.func, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@JackTheRipper removed the PropTypes.node
, because PropTypes.func
is sufficient
* frint-router: caching. * frint-router: coverage. * frint-router: caching docs. * examples: require packages from local node_modules.
NOTE: this PR should be rebase-merged to keep multiple contributors name in the history
Will be merged by @fahad19 once approved.
What's done
Introduced two new packages:
frint-router
frint-router-react
Documentation can be found in the diff.