I am using React-Router 4 to create some routes. (It uses the PrivateRoute component from the official docs but that's not relevant to this question.)
<HashRouter>
<Switch>
<PrivateRoute exact path='/' component={Wrapper(Home)} />
<Route exact path='/login' render={(props) => <Login authenticate={this.authenticate} {...props} />} />
<PrivateRoute exact path='/clients' component={Wrapper(Clients)} />
</Switch>
</HashRouter>
As you can see the Home and Clients components are wrapped in an HOC. For the moment this does nothing:
const Wrapper = (Page) => {
return (props) => (
<div className="page">
<p>This should be on every page</p>
<Page {...props} />
</div>
);
}
This works fine. What I can't figure out is how to wrap the Login route in the same HOC. I've tried
<Route exact path='/login' render={(props) => Wrapper(<Login authenticate={this.authenticate} {...props} />)} />
But this returns the error:
Route.render(): A valid React element (or null) must be returned.
Can you try doing it like that:
<Route exact path='/login' render={(props) => Wrapper(Login)({...props, authenticate: this.authenticate})} />
Because now Wrapper HOC is returning a function, but the react element is expected. To get the react element we'll need to call this function with the needed props and that seems like a good place to add that extra prop this.authenticate.
Here's a quick snippet I made:
https://stackblitz.com/edit/react-zestnq
Related
I am using the react-router-dom and creating some routes in my application. Can anyone please explain me the specific usages of and . What will be the difference affect in the rendering if there are any. I will include a sample code snippet.
import { BrowserRouter as Router, Route } from 'react-router-dom';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<Router>
<Route path="/home" component={App} />
<Route path='/about'>
<About />
</Route>
</Router>
);
I tried both and both are working fine. But I was unable to find the specific usage of one over other.
The react-router-dom#5 Route component has 4 ways to render content. The primary method is to directly render content as the children prop. Note here that no route props will be passed to the component.
<Route path='/about'>
<About />
</Route>
The other 3 ways are to use one of the route render methods.
The recommended method of rendering something with a <Route> is to use
children elements, as shown above. There are, however, a few other
methods you can use to render something with a <Route>. These are
provided mostly for supporting apps that were built with earlier
versions of the router before hooks were introduced.
<Route component>
<Route render>
<Route children> function
Examples:
component - Other common method, route props are implicitly passed as props to the component.
<Route path='/about' component={About} />
render function - Alternative to pass along runtime props as well as the route props.
<Route path='/about' render={routeProps => <About {...routeProps} {...otherProps} />} />
children function - Renders regardless of route match and passes route props to component to handle any conditional logic.
<Route path='/about' children={routeProps => <About {...routeProps} />} />
Just a small add to Mr. Drew Reese's answer, the way rendering a component directly inside <Route> allow you to freely pass your own props inside your component (it's more familiar to the way we usually do with normal React components).
Ex: You have a page About, inside, it includes 3 tabs: "About me", "About my team", "About my blog".
const About = () => {
// call APIs, handle...
return (
<>
<Switch>
<Route path='/about-me'>
<Me data={dataMe} />
</Route>
<Route path='/about-team'>
<Team data={dataTeam} />
</Route>
<Route path='/about-blog'>
<Blog data={dataBlog} />
</Route>
</Switch>
</>
)
}
I would like to pass an ID in props to a component react
I use react-router-dom
Here is my app.js file
<Switch location={this.props.location}>
<Route exact path="/" component={Home} />
<Route path="/list" component={List} />
<Route path='/img/:imgId' component={() => <Img imgId={this.props.params.imgId}/>} />
</Switch>
When I go to the next url img / 2, the router sends me the right page, but the id is not present in the props.
When I look into react developer tools on chrome, I can see that
<Switch>
<Route>
<component>
<Img>
</Img>
</component>
</Route>
</Switch>
In the component called component, I have something in props.match.params.imgId
But when I go on the Img component, here is what I have as props:
imgId: {empty object}
Do you know how to recover the id in parameter?
Thanks :)
You should do it like this:
1st: change your Route declaration
<Switch location={this.props.location}>
<Route exact path="/" component={Home} />
<Route path="/list" component={List} />
<Route path='/img/:imgId' component={Img} />
</Switch>
2nd: you should access the prop from match injected by react-router like in this example
const Img = ({ match }) => (
<div>
<h3>IMAGE ID: {match.params.imgId}</h3>
</div>
);
but of course you can easily adapt that code into your own.
You would use the functional callback pattern in case where you want to pass some props other than router props to the component. In your case you can simply render the Img component
<Switch location={this.props.location}>
<Route exact path="/" component={Home} />
<Route path="/list" component={List} />
<Route path='/img/:imgId' component={Img} />
</Switch>
and access the imgId in Img component like this.props.match.params.id.
However to point out the problem in your current code, it doesn't work correctly because you are trying to pass the parents match props to the Img component whereas you need to pass the Route's own props like
<Switch location={this.props.location}>
<Route exact path="/" component={Home} />
<Route path="/list" component={List} />
<Route path='/img/:imgId' component={(routerProps) => <Img imgId={routerProps.match.params.imgId}/>} />
</Switch>
I wrote my application in a very modular way in the way that each package (section of the application) is a package and the only thing i'm returning from each package is a <Route to="/dest" component="something" />.
The problem that i'm having right now is that, this method of returning are not really working in and that's how i tested it:
The initial setup was something like this :
<Switch>
<Dashboard {...rest} />
<Users {...rest} />
<Quiz {...rest} />
</Switch>
And the Dashboard component is something like this :
...
return (
<Route exact path="/" render={ props => (
<Dashboard {...props} {...stuff} />
)}/>
)
and almost the same thing for User and Quiz components, which are directly being imported from their module packages.
when i run the application, i can access Dashboard & Users but Quiz is not being rendered.
if i change the order and put quiz on top of <Users /> it will work. which means it only can render one after first exact route.
but instead, if i take all those code and write the Routes in the switch itself without referencing to another component, it works like a charm.
<Switch>
<Route exact path="/" render={ props => (
<div>demo</div>
)}/>
<Route path="/dashboard" render={ props => (
<div>demo</div>
)}/>
<Route path="/users" render={ props => (
<Users />
)}/>
<Route path="/quiz" component="Users"/>
</Switch>
I've tried both component, and render in route, this one is just a sample
any suggestion on why is this happening ?
UPDATE 1
I haven't managed to figure out what the problem is and what's causing it, but i found a way around it by creating a component that returns a Switch and direct Routes as children. then i exported the Switch to my main app.
<Switch>
<Dashboard {...rest} />
<Users {...rest} />
<Quiz {...rest} />
</Switch>
This will not work because Switch expects Routes to be direct children. It cannot perform its logic on arbitrary wrapper components.
I not sure if that would work but you would have to directly export Route from the module:
Dashboard.js
export default (
<Route exact path="/" render={ props => (
<Dashboard {...props} />
)}/>
)
but when you do something like this:
export default (stuff) => (
<Route exact path="/" render={ props => (
<Dashboard {...props} {...stuff} />
)}/>
)
Notice that you are not exporting a Route but a functional component that renders Route.
I personally think that you should only export purely components to be rendered without Routes and leave that up to library consumer.
I have a React/Redux app and if a user hasn't finished the signup process if they log in again I want them to be redirected to the signup route for the step they are on no matter what route they try to go to. What's the best way for me to do this without having to make a custom route for every single one of my routes with that logic inside? Here is my basic router:
render() {
return (
<Router history={history}>
<Switch>
<HomeRoute exact path="/" publicComp={Start} privateComp={Rooms}/>
<Route path="/login" component={Login}/>
<Route path="/signup" component={SignUpMain}/>
</Switch>
</Router>
);
}
Ideally, something where I can just say "on all of these routes if signup isn't complete redirect to /signup". Is there a way to do this?
One way would be to replace your Route components with a higher order component that redirects to signup.
const RedirectRoute = ({ component: Component, ...rest }) => (
<Route
{...rest}
render={props =>
<Redirect
to="/signup"
/>
}
/>
);
If you add the higher order components to the bottom of the file with your Router above then you can just replace the Route components like so...
render() {
return (
<Router history={history}>
<Switch>
<RedirectRoute exact path="/" render={() => <HomeRoute exact path="/" publicComp={Start} privateComp={Rooms}/>}/>
<RedirectRoute path="/login" component={Login}/>
<Route path="/signup" component={SignUpMain}/>
</Switch>
</Router>
);
}
If you want to keep the higher order component in a seperate file then just import it like you would any other component.
I have a problem with my react components.
Basically my interface should be an SPA, built with ReactJS.
During the implementation of the authentication with auth0-js I also implemented some routes.
The layout looks like this:
Whenever I click now on the "Recipes" link, it should redirect to the route "/recipes".
However, when I implement the route with <Route path="/recipes" component={Recipes} /> it only renders what is actually returned in the Recipes component. I know that this is correct from what react does.
Now, I want to keep the navigation bar but only want to exchange the component below, so I want to change what is below the navigation bar like I did in App.js.
How can I achieve this? Is something about the routes, or components not correct?
I do not want to re-render always everything. I also want to keep the style of the whole page. Is there a way to do this?
The whole code can be found here.
I agree with Jyutzio in that you need to move the Navigation component above the child routes to only change to content of the child routes.
In order to have the Navigation bar update with logged in / logged out state you may want to consider implementing redux. I have a project with nearly the exact requirements as yours - a navigation header that is static.
In my header I have import { connect } from 'react-redux';
At the bottom of the component I use connect before exporting:
function mapStateToProps(state) {
return { authenticated: state.auth.authenticated };
}
Header = connect(mapStateToProps)(Header);
export default Header;
Then this allows me to check the "authenticated" piece of state and render accordingly.
renderLogoutButton() {
if(this.props.authenticated) {
return(
<li><a onClick={...}>Logout</a></li>
);
} else {
return(
<li><a onClick={...}>Login</a></li>
);
}
}
You will need to setup a reducer, but there are many resources explaining redux setup.
The router (simplified) I have set up as follows:
import Admin from './index';
...
<BrowserRouter>
<Switch>
<Route exact path="/login" component={Login} />
<Route path="/" component={Admin} />
</Switch>
</BrowserRouter>
index:
import AdminRouter from './admin/admin_router';
...
<div>
<Menu />
<div>
<AdminRouter />
</div>
</div>
admin_router:
<div>
<Switch>
<Route exact path="/" component={Home} />
<Route exact path="/details" component={AdminDetails} />
<Route component={PageNotFound} />
</Switch>
</div>
If you place your Navigation component above the switch, it will solve your problem.
const Routes = () => (
<Router history={history} component={Home}>
<Route component={Navigation}/>
<Switch>
<Route exact path="/" render={props => <Home auth={auth} {...props} />} />
<Route path="/home" render={props => <Home auth={auth} {...props} />} />
<Route
path="/callback"
render={props => {
handleAuthentication(props);
return <Callback {...props} />;
}}
/>
<Route component={NotFound} />
</Switch>
</Router>
);