-
-
Notifications
You must be signed in to change notification settings - Fork 10.4k
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
Support fragments in <Switch /> #5892
Conversation
expect(node.innerHTML).toMatch(/two/); | ||
}); | ||
|
||
it("does handle nested fragments", () => { |
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 specifically should not support this. We don't support nested routes in a Switch, so this behavior difference will undoubtedly cause confusion. To be clear, I see this:
<Switch>
<Route path="/foo" component={Foo} />
<>
<Route path="/bar" component={Bar} />
<Route path="/baz" component={Baz} />
</>
</Switch>
as equivalent to this:
<Switch>
<Route path="/foo" component={Foo} />
{[barRoute, bazRoute].map(route => <Route path={route.path} component={route.component} />)}
</Switch>
Fragments are basically convenience functions for arrays. So, I'd treat them as such to maintain the status quo with Switch's behaviors.
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.
Actually React.Children.forEach
recurses over nested arrays. Meaning: react-router always supported this very same case, i.e. arrays in arrays. For clarification, I added more test cases that use arrays instead of fragments. The behavior is only consistent when recursing over fragments.
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.
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.
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.
One might argue that this is an inconsistency within React itself. Related:
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.
Actually, I'm not. Sorry, just busted out some code here and contradicted myself. Apologies for the flip flop!
React.Children.count([<div/>, <div/>, [<div />, <div/>]]) == 4
React.Children.count([
<div/>,
<div/>,
<React.Fragment>
<div/>
<div/>
</React.Fragment>
]) == 3
Fragments are not the same as arrays, they just have a similar use case. The post here is clarifying: facebook/react#11859 (comment)
I'd much rather do React.Children.map(children, child => child.type === React.Fragment ? child.props.children : child).forEach(child => ...)
. It's not common to have nested child arrays inside of <Switch>
, so trying to emulate that behavior with something that's intentionally inconsistent.
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 do not see why fragments should be handled differently for all intents and purposes of react-router than arrays. They are element containers and should be handled accordingly.
Design decisions have been made for fragments which result in different behavior for React.Children.count
, React.Children.map
and others when comparing fragment and array behavior. This difference in behavior stems from important differences when handling these containers as part of render
. React-router doesn't have to behave different when encountering fragments vs. arrays.
Furthermore, it makes no sense for react-router to try to matchPath
a fragment element. This will never work.
Lastly, there is no technical reason why fragment recursion cannot be supported. There is no additional cost associated to it. It is faster than a map
followed by a forEach
. It makes array/fragment behavior consistent.
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 do not see why fragments should be handled differently for all intents and purposes of react-router than arrays.
Because they're not arrays. I'd really rather not try to go against the upstream behavior semantics, even if it is admittedly weird. We got burned in previous versions by trying to do too much of React's behavior on our own. The edge cases are death by a thousand cuts.
I'm still on Team No Flatten™, but the rules dictate we have two maintainers approve before merging. If anyone else wants to voice their opinions, even if it's different from my own, they can and we can work towards a resolution.
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.
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.
Added the requested example to the issue:
d7a13b5
to
6c1be62
Compare
@@ -1,4 +1,4 @@ | |||
import React from "react"; | |||
import React, { Fragment } from "react"; |
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 just reference properties of the React
object everywhere else (React.Component
, React.Children
), so we should probably just use React.Fragment
. If I recall correctly, the { Fragment }
style of destructured imports is actually a Babel feature (there is no actual Frament
export from React).
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.
Fair point. Changed.
6c1be62
to
2ba616d
Compare
fb3a586
to
f9481bb
Compare
} | ||
} | ||
|
||
function getMatch(route, location, children) { |
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.
Can we move this within the component? We don't need it externally and then we can access the props/context directly.
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 can move it within the component sure, but do know that this was a deliberate decision for the sake of efficiency. There is no need to redefine it over and over again within render
. Especially considering that it does not need access to this
, props
or state
.
Please let me know whether you still would like this to be inlined.
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.
@bripkens moving it within the component doesn't necessarily mean defining it within render, right? It could go on the instance, or possibly as a static class method if React Router transpiles those down.
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.
Whoops, I thought the component was just a function when I looked at it again on GitHub. Sorry for that! Changed it accordingly.
When checking for matched routes in <Switch />, traverse fragments to determine the matched route.
2ba616d
to
1d0d42f
Compare
What's your use case, @bripkens? I can't really think of a compelling use case for having a The only thing I can imagine that you might want to do is return multiple The lack of a clear use case, plus the fact that React treats arrays differently from |
@mjackson i listed a use case over in #5785 (comment) . If you read that issue and other similar issues, there are a handful of what I think to be other good use cases from developers. tl;dr keys are unnecessary boilerplate with groups of routes. Fragments let you group the routes together in a way that adds no extra boilerplate. Whether that is because they are dynamically generated, or because they need to be grouped for auth permissions, or just because a developer wants to group them based on their role in the app.
🤷♂️ the users of React Router are not going to care about this difference. They are just going to want to use Fragments so that they don’t have to specify keys when working with groups of routes within a single Switch. |
When checking for matched routes in
<Switch />
, traverse fragments todetermine the matched route.
Fixes #5785