React-router-dom: Render two components under one route - javascript

<Provider store={store}>
<BrowserRouter>
<div>
<Switch>
<Route
path="/test/:id"
component={Question1} />
<Route
path="/test/:id"
component={Question2} />
</div>
</BrowserRouter>
</Provider>
I need to navigate from Question1, which has id of 1, to question2 which should have id of 2. I can navigate to question1 from test without problem, but when I want to go to question to using this.props.history.push('/test/2') the URL changes but I dont get navigated to question2.
Any ideas ?

You should have a single Question component which handles the id and displays the appropriate question.
You can do the same using the render method of Route
<Route
path="/test/:id"
render={(props)=>{
switch(props.match.params.id){
case "1": return <Question1/>;
case "2" : return <Question2/>;
default : return null;
}
}}
/>
EDIT: Corrected code after comment

This issue is that <Switch> will match the first component under the <BrowserRouter/> in this case. If it doesn't match the first path, it will try the second. That is how Switch works. So in your example the path template of path=/test/:id actually matches both /test/1 and /test/2, and that Route always renders Question1 and never Question2. Check out the docs here
https://reacttraining.com/react-router/web/api/Switch.
One quick thing you can do is if you know you only have two Questions then you can do
<Route exact
path="/test/1"
component={Question1}
/>
<Route exact
path="/test/2"
component={Question2}
/>
Which will solve your problem immediately, but I don't recommend this as it gets unreasonable as you get more Questions. A better set-up is what #palsrealm recommends.

You can do this with one route, and load the right question in your component :
<Route path="/test/:id" component={Question} />
// Question.js
export default React.createClass({
render() {
return (
<div>
<h2>Question {this.props.params.id}</h2>
</div>
)
}
})
doc : route-with-parameters

Related

React router rendering

I have a piece of code that I can not understand. Any explanation is appreciated. My question is what (props) actually is doing here and where it comes from? If I take "props" out, my code is not working.
<Route
exact
path='/'
render={(props) =>
!isAuthenticated ? (
<Landing {...props} />
) : (
<Redirect to='/admin' />
)
}
/>
Route is a component of react-router , which matches the url and routes , The code you have given is used for Authentication to redirect user if he is not Authorized ,whatever that is passed from parent has to be passed to the children in this case , the reason they are passing props is to pass the properties of React Router which includes things like history,location and etc. A simple console.log will show you what it is passing and why it is required
Refer to Image below to see what all is getting passed

Why aren't {...props} necessary when passing them to a component in a router in React?

I've started learning React recently and when I was following a course I stumbled upon something that confused me a lot. I decided to try it out separately to understand it and I don't get it.
I've looked it up and when you use a Route Tag in React, you do it like this:
<Route path="/" component={Component} />
But when you want to pass props, the syntax is the following:
<Route path='/dashboard' render={(props) => (
<Dashboard {...props} isAuthed={true} />
)}
/>
Which at first confused me, but then I got it. When testing, I did the following:
<Route path="/" render={() => (
<PropDrilling user={"Someone"} />
)}>
When I test this in the component, console.logging this.props, it works and I don't know why.
Thanks in advance!
React router uses React's render props where react router uses match, location and history. So, when you don't provide props in the render function, it will still be able to show you react component props. But it would not include match, location and history props provided by react router. To use them, you must provide props parameter in the render function.
Using an analogy, consider
function test(obj) {
console.log(obj);
}
test({a : 1});
function test() {
console.log({a : 1});
}
test();
These two functions log the same result, but one has no information about the data passed to console.log, while the other does.
Similarily, {...props} means you are forwarding the props received as a function parameter to a component, irrespective of what the value of props is.
Taking the example of <Route path="/" component={Component} />, you could write it like that :
const myProps = {
path: "/",
component: <YourComponent />
}
<Route {...myProps} />
Consider every props passed to a component as a key in an object. Here is a small repro on Stackblitz to show you how it works.

Why pass {...props} in a React Route?

I'm using the route below just because it was the code I found on the web:
<Route exact path="/test" render={(props) => <Test {...props} msg={ "abc" } /> } />
I know the {...props} denotes multiple arguments but I can't understand why I need it at all because the code below also works just fine and props.msg is available in Test expected
<Route exact path="/test" render={() => <Test msg={ "abc" } /> } />
So what does {...props} actually do when passed in during render?
From the documentation:
The render prop function has access to all the same route props (match, location and history) as the component render prop.
If Test is not using any of these then you don't have to pass them.
For your use case, your second code snippet is sufficient and will work just fine.
Spreading the props into the child component like
<Route exact path="/test" render={(props) => <Test {...props} msg={ "abc" } /> } />
makes sense if you want to pass properties to the child component that you are not handling yourself but receiving from another source like the Route-Component itself (in particular the route props "match", "location" and "history").
Keep in mind you can have parameters in your route, ex:
/users/:username
Those props will allow you to access it. In your case, you probably don't need it but you're better to always include them so it's consistent.
https://reacttraining.com/react-router/web/api/Route/route-props
more doc about the 3 props that are provided :
match https://reacttraining.com/react-router/web/api/match
location https://reacttraining.com/react-router/web/api/location
history https://reacttraining.com/react-router/web/api/history

Is React Router causing my component to render twice?

Background
I have a list of items. If you click on a detail link under an item, you are taken to its page where it allows you to read and add comments about that particular item. Currently, the comments component is rendering both on the index page and on the item details page. It should only render on the latter.
Can you help me figure out why?
Troubleshooting
I checked my routes and my Comments and Comment components but did not see any obvious errors. I saw this post about rendering components twice, but my situation is different because I have to use render= to pass down props.
I'll admit, I have never tried to build a route like this, so maybe I am not approaching it properly. The one is question is the 2nd from the bottom in this list below.
Routes
render(){
return(
<div>
<Switch>
<Route exact path='/' render={(routerProps) => <Programs {...routerProps} programs={this.props.programs}/>} /> />
<Route exact path='/programs' render={(routerProps) => <Programs {...routerProps} programs={this.props.programs} />} />
<Route path='/programs/new' render={(routerProps) => <ProgramInput {...routerProps}/>}/>
<Route path='/programs/:id' render={(routerProps) => <Program {...routerProps} programs={this.props.programs}/>}/>
<Route exact path='/programs/:id/comments' render={(routerProps) => <Program {...routerProps} program={this.props.program}/>}/>
<Route exact path='/watchlist' render={(routerProps) => <Program {...routerProps} programs={this.props.programs} />} />
</Switch>
</div>
)
}
CommentsContainer
class CommentsContainer extends React.Component {
render(){
return(
<div>
<Comments comments={this.props.program && this.props.program.comments}/>
</div>
)
}
}
export default CommentsContainer
Program (where CommentsContainer is rendered)
I took out some of the Card code for brevity. There is More link inside the card that takes you to the item page. That part works fine.
return(
<Fragment>
<Grid.Column>
<Card as='div'>
</Card>
</Grid.Column>
<CommentsContainer program={program}/>
</Fragment>
)
}
Comments
const Comments = (props) => {
// console.log(props.comments && props.comments.map(comment => console.log(comment))
return(
<Comment.Group>
{props.comments && props.comments.map(comment => <CommentCard key={comment.id} comment={comment}/>)}
</Comment.Group>
)
}
export default Comments
ComentCard
I don't think the issue lies here, but here's a link if you need to see the file.
Backend
I am using Rails API for my backend. Here is a link to my comments_controller if you want to take a peek.
Thank you for your time and advice!
If you only want the CommentsContainer to show when there are comments, you can just replace the line where it's rendered with:
{program.comments && <CommentsContainer program={program}/>}
As Matt Oestreich pointed out earlier you always render comments when you render the Program component. The Programs (plural) component is basically a list that consists of Program components. So when you render the Programs component you always render the CommentsContainer with the Comment Component.
So maybe you can make another component, the ProgramDetails Component where you include the CommentsContainer. In the Program Component you can link to the ProgramDetails Component. I think this would prevent that the comments render in the Programs (plural) is rendered.
This did end up being a routes issue. Both my /programs and my / routes were displaying my CommentsContainer because I needed to add a ternary to essentially filter those routes out.
I added this code in my Programs (plural) component right after my Card component. It uses props.match to check if the route being rendered was NOT /programs and NOT /. When that statement evaluated to true, the CommentsContainer and associated comments were rendered on the /programs/:id. If either of those statements evaluated to true, it didn't render anything.
{this.props.match.url !== '/programs' && this.props.match.url !== '/'? <CommentsContainer program={program}/> : null}
Since I only passed down routerProps in my Programs.js file, I had to pass down props.match from Programs to my Program component (which renders the cards and CommentContainer). Doing this allows the ternary above to work.
{props.programs && props.programs.map((program) => <Program key={program.name} program={program} match={props.match}/>)}

router changed but component does not rerender

I'm using react-router v4 and redux. I have a react-router event like so
//src/App/components/common/Notifications
onCommentClick = (id) => {
this.props.history.push(`/dashboard/${id}`)
}
It will change the browser url, my route is setup correctly I guess
//src/App/containers/dashboard/user/NotificationDetail
renderUserRoute() {
return (
<Switch>
<Route exact path="/dashboard" component={UserAreaGuarded} />
<Route exact path="/dashboard/:id" component={NotificationDetail} />
</Switch>
)
}
but the problem is redux doesn't seem to rerender my container component, I need to refresh to get the right content.
I created a repo just to demo this issue https://github.com/thian4/hoc-redux, been stuck for so long for this, couldn't find any clue why it doesn't rerender.
The problem was not with the router, the NotificationDetail component invokes fetchNotification only in componentDidMount. As you are rendering the route components with the Route component prop, the NotificationDetail route component is mounted only once and then merely updated on every re-render.
There are two ways to fix this...
In NotificationDetail, do the fetchNotification stuff in componentDidUpdate instead of componentdidMount, simply rename the method. This approach is better.
Use a stateless component as the value of component prop in dashboard\index.js:
renderUserRoute() {
return (
<Switch>
<Route exact path="/dashboard" component={UserAreaGuarded} />
<Route exact
path="/dashboard/:id"
component={props => <NotificationDetail {...props} />} />
</Switch>
)
}
This will make React-Router render a new route component every time the route changes. Obviously this has performance issues over #1.
Unrelated to this, please add a key={i} on this line in Notification.js to fix the dreaded Each child in an array or iterator should have a unique "key" prop. warning.
Hope this helps.

Categories

Resources