The App can't switch and render All the routes. It gets only one path (firstOne) and renders it.
import React, { Component } from "react";
import { Route, Switch } from "react-router-dom";
import { connect } from "react-redux";
class Board extends Component {
state = {
routes: []
};
showRoutes = routes =>
routes.length > 0 &&
routes.map((route, i) => (
<Route key={i} path={route.path} component={()=>"path"+i} />
));
render() {
const { routes } = this.props;
return (
<div>
<Switch>
<Route exact path="/" render={() => "start page"} />
{this.showRoutes(routes.routesApi)}
<Route path="/" render={() => "no such routes"} />
</Switch>
</div>
);
}
}
const mapStateToProps = state => ({
routes: state.routes
});
export default connect(mapStateToProps)(Board);
I have also used componentWillReceiveProps() as:
componentWillReceiveProps(NextProps) {
this.setState({ routes: NextProps.routes });
}
and switch to read data from state, but the result is the same.
Could you help me please to understand what is wrong?
I'm trying to trigger a redirect if a user is logged in. A successful login triggers an update of this.state.user so I'd like to handle the redirect in componentDidUpdate() or another lifecycle method.
The if statement is getting called when I intend for it to, but the redirect does nothing. Any idea as to how I can fix this? I just want this to update the url so it doesn't necessarily need to use Redirect.
I'm not using user authentication currently and don't intend to add it yet.
import React, { Component } from "react";
import "./App.css";
import { BrowserRouter as Router, Route, Redirect } from "react-router-dom";
import AuthContainer from "./components/AuthContainer";
import ChatSelector from "./components/ChatSelector";
import { debug } from "util";
// import ChatRoomContainer from './components/ChatRoomContainer';
class App extends Component {
constructor(props) {
super(props);
this.state = {
user: {}
};
}
setUser = user => {
console.log("setting user");
this.setState({ user });
};
componentDidUpdate() {
// if a user is logged in, redirect them to chat-selector
if (Object.keys(this.state.user).length > 0) {
console.log(this.state.user);
<Router>
<Redirect to="/chat-selector" />;
</Router>;
}
}
render() {
return (
<Router>
<div>
<Route
exact
path="/"
render={props => (
<AuthContainer {...props} setUser={this.setUser} />
)}
/>
<Route
exact
path="/chat-selector"
render={props => <ChatSelector {...props} user={this.state.user} />}
/>
{/* <Route exact path='/chatroom' component={ChatRoomContainer}/> */}
</div>
</Router>
);
}
}
export default App;
I solved this by placing the if statement within render, and adding a redirect boolean to state.
import React, { Component } from "react";
import "./App.css";
import {
BrowserRouter as Router,
Route,
Redirect,
withRouter
} from "react-router-dom";
import AuthContainer from "./components/AuthContainer";
import ChatSelector from "./components/ChatSelector";
import { debug } from "util";
// import ChatRoomContainer from './components/ChatRoomContainer';
class App extends Component {
constructor(props) {
super(props);
this.state = {
user: {},
redirect: false
};
}
setUser = user => {
console.log("setting user");
this.setState({ user });
};
redirect = () => {
this.setState({ redirect: true });
};
render() {
if (
Object.keys(this.state.user).length > 0 &&
this.state.redirect === true
) {
this.setState({ redirect: false });
console.log("logged in");
return (
<Router>
<Redirect to="/chat-selector" />
</Router>
);
} else {
console.log("not logged in");
}
return (
<Router>
<div>
<Route
exact
path="/"
render={props => (
<AuthContainer
{...props}
setUser={this.setUser}
redirect={this.redirect}
/>
)}
/>
<Route
exact
path="/chat-selector"
render={props => <ChatSelector {...props} user={this.state.user} />}
/>
{/* <Route exact path='/chatroom' component={ChatRoomContainer}/> */}
</div>
</Router>
);
}
}
export default App;
There is actually a better way of doing this, and I have recently stumbled across a similar situation.
Since the <Redirect /> technique does not work well with helper functions or lifecycle methods, I suggest to instead use this.props.history.push() inside the ComponentDidUpdate() to perform a redirect. Just remember to wrap your component with the withRouter() HOC.
Example code here: http://blog.jamesattard.com/2018/03/fire-action-creator-after-react-state.html
const rootEl = document.getElementById('root');
ReactDOM.render(
<BrowserRouter>
<Switch>
<Route exact path="/">
<MasterPage />
</Route>
<Route exact path="/details/:id" >
<DetailsPage />
</Route>
</Switch>
</BrowserRouter>,
rootEl
);
I am trying access the id in the DetailsPage component but it is not being accessible. I tried
<DetailsPage foo={this.props}/>
to pass parameters to the DetailsPage, but in vain.
export default class DetailsPage extends Component {
constructor(props) {
super(props);
}
render() {
return (
<div className="page">
<Header />
<div id="mainContentContainer" >
</div>
</div>
);
}
}
So any idea how to pass the ID on to the DetailsPage ?
I used this to access the ID in my component:
<Route path="/details/:id" component={DetailsPage}/>
And in the detail component:
export default class DetailsPage extends Component {
render() {
return(
<div>
<h2>{this.props.match.params.id}</h2>
</div>
)
}
}
This will render any ID inside an h2, hope that helps someone.
If you want to pass props to a component inside a route, the simplest way is by utilizing the render, like this:
<Route exact path="/details/:id" render={(props) => <DetailsPage globalStore={globalStore} {...props} /> } />
You can access the props inside the DetailPage using:
this.props.match
this.props.globalStore
The {...props} is needed to pass the original Route's props, otherwise you will only get this.props.globalStore inside the DetailPage.
Since react-router v5.1 with hooks:
import { useParams } from 'react-router';
export default function DetailsPage() {
const { id } = useParams();
}
See https://reacttraining.com/blog/react-router-v5-1/
Use render method:
<Route exact path="/details/:id" render={(props) => (
<DetailsPage id={props.match.params.id}/>
)} />
And you should be able to access the id using:
this.props.id
Inside the DetailsPage component
In addition to Alexander Lunas answer ...
If you want to add more than one argument just use:
<Route path="/details/:id/:title" component={DetailsPage}/>
export default class DetailsPage extends Component {
render() {
return(
<div>
<h2>{this.props.match.params.id}</h2>
<h3>{this.props.match.params.title}</h3>
</div>
)
}
}
Use the component:
<Route exact path="/details/:id" component={DetailsPage} />
And you should be able to access the id using:
this.props.match.params.id
Inside the DetailsPage component
This is for react-router-dom v6 (I highly suggest using functional components for this)
It's somewhat painful for react-router-dom to keep changing syntax and rules. But here goes nothing.
You can use both useParams and useSelector to solve this
import { useParams } from 'react-router';
import { useSelector } from 'react-redux';
const Component = () => {
const { id } = useParams(); //returns the :id
const page = useSelector((state) => state.something[id]); //returns state of the page
return <div>Page Detail</div>;
}
export default Component;
BUT, the problem persist when you also have an action creator and you want to pass it as a props in connect function
export const connect(mapStateToProps, mapDispatchToProps)(Component)
since we are using useParams, it won't be passed to mapStateToProps that we created
const mapStateToProps = (state, ownProps) => {
console.log(ownProps) //wont recognize :id
//hence
return {
someReducers: state.someReducers[id] //would return an error: 'id' is not defined
};
};
on the other hand, you can't entirely ignore the connect function since you need mapDispatchToProps to work with your component.
The workaround to this is to create a Higher Order Component withRouter function yourself. This was a deprecated react-router-dom helper.
//make this
import { useParams, useLocation, useNavigate } from 'react-router';
import { connect } from 'react-redux';
import { yourActionCreator } from '../actionCreator';
const withRouter = (Child) => {
return (props) => {
const location = useLocation();
const navigation = useNavigate();
const params = useParams();
return (
<Child
{...props}
params={params}
navigate={navigate}
location={location}
/>
);
};
};
const Component = () => {
// your component...
return <div> Page Detail </div>
};
export mapStateToProps = (state, ownProps) => {
console.log(ownProps) // would contain the :id params
return {
//something
}
};
const mapDispatchToProps = {
yourActionCreator
}
export withRouter(connect(mapStateToProps, mapDispatchToProps)(Component));
Here's typescript version. works on "react-router-dom": "^4.3.1"
export const AppRouter: React.StatelessComponent = () => {
return (
<BrowserRouter>
<Switch>
<Route exact path="/problem/:problemId" render={props => <ProblemPage {...props.match.params} />} />
<Route path="/" exact component={App} />
</Switch>
</BrowserRouter>
);
};
and component
export class ProblemPage extends React.Component<ProblemRouteTokens> {
public render(): JSX.Element {
return <div>{this.props.problemId}</div>;
}
}
where ProblemRouteTokens
export interface ProblemRouteTokens {
problemId: string; }
Another solution is to use a state and lifecycle hooks in the routed component and a search statement in the to property of the <Link /> component. The search parameters can later be accessed via new URLSearchParams();
<Link
key={id}
to={{
pathname: this.props.match.url + '/' + foo,
search: '?foo=' + foo
}} />
<Route path="/details/:foo" component={DetailsPage}/>
export default class DetailsPage extends Component {
state = {
foo: ''
}
componentDidMount () {
this.parseQueryParams();
}
componentDidUpdate() {
this.parseQueryParams();
}
parseQueryParams () {
const query = new URLSearchParams(this.props.location.search);
for (let param of query.entries()) {
if (this.state.foo!== param[1]) {
this.setState({foo: param[1]});
}
}
}
render() {
return(
<div>
<h2>{this.state.foo}</h2>
</div>
)
}
}
FOR version 6 ( 2022 )
Note: using useParams you can easily get your params in your component.
look at the example below
import React from "react";
import { BrowserRouter as Router, Routes, Route, Link } from "react-router-dom";
import Home from "./compo/home";
import About from "./compo/about";
import Login from "./compo/login";
import "./styles.css";
const App = () => {
return (
<Router>
<div className="container">
<Link to="/home">Home</Link>
<Link to="/about">About</Link>
<Link to="/login">Login</Link>
</div>
<Routes>
<Route path="/home" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/login" element={<Login />} />
<Route path="/login/:name" element={<Login />} />
</Routes>
</Router>
);
};
export default App;
Login Component
import { useParams } from "react-router-dom";
const Login = () => {
let { name } = useParams();
return <h1>i am {name ? <b>{name}</b> : "login"}</h1>;
};
export default Login;
if you are using class component, you are most likely to use GSerjo suggestion. Pass in the params via <Route> props to your target component:
exact path="/problem/:problemId" render={props => <ProblemPage {...props.match.params} />}
In the latest version of (react-router-dom#6.3.0), you can do it like this:
<Route path="path" element={<YourComponent type="simple" />} />
Here, type is the input passed to YourComponent
I was working on react-router-dom version 6.3.0 and above solution didn't resolve my problem. Then I use something like this and it worked:
<Route exact path='/payment-status/:userId/:orderId' element={<PaymentStatus/>}/>
And on PaymentStatus.js page I did like this:
import { useParams } from 'react-router-dom'
export const PaymentStatus = () => {
let {userId, orderId}=useParams()
return (
<div>
<h2>order ID : {orderId}</h2>
<h2>user ID : {userId}</h2>
</div>
)
}
It worked for me. I hope it may help someone. Thanks!
try this.
<Route exact path="/details/:id" render={(props)=>{return(
<DetailsPage id={props.match.params.id}/>)
}} />
In details page try this...
this.props.id
Simple example with Class, HoC and Router v5
package.json
"react-router-dom": "5.3.1",
"react-router": "5.3.1",
"#types/react-router-dom": "5.3.3",
// YourComponent.tsx
import React from 'react';
import { RouteComponentProps, withRouter } from 'react-router';
export interface PathParams {
id: string;
}
export interface Props extends RouteComponentProps<PathParams> {}
export interface State {}
class YourComponent extends React.Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = {};
console.log(props.match.params) // { id: 1 }
// TypeScript completions
console.log(props.match.params.id) // 1
}
render() {
return <></>;
}
}
export default withRouter(YourComponent);
// App.tsx
import './App.css';
import React from 'react';
import { Route, Switch, Router } from 'react-router-dom';
import YourComponent from './YourComponent';
function App(): JSX.Element {
return (
<Router>
<Switch>
<Route
path="/details/:id"
component={() => <YourComponent />}
/>
</Switch>
</Router>
);
}
export default App;
I have following routing configuration:
return (<div>
<Router>
<div>
<Route path='/login' component={LoginPage}/>
<EnsureLoggedInContainer>
<Route path='/abc' component={abc} />
</EnsureLoggedInContainer>
</div>
</Router>
</div>
);
The EnsureLoggedInContainer is:
import React from 'react';
import { connect } from "react-redux";
class EnsureLoggedInContainer extends React.Component
{
componentDidMount() {
if ( !this.props.isLoggedIn )
{
// this.props.history.push('/login');
this.context.router.push('/contact');
}
}
render() {
// console.log(this.props);
if ( this.props.isLoggedIn )
{
return this.props.children;
}
else
{
return null;
}
}
}
const mapStateToProps = (state,ownProps) => {
return{
isLoggedIn : state.isLoggedIn,
// currentURL : this.props
}
}
export default connect(mapStateToProps)(EnsureLoggedInContainer);
But, the history push: this.props.history.push('/login'); isn't working. Here history is not present.
If I am using a configuration like this:
<Route component={EnsureLoggedInContainer}>
<Route path='/myjs' component={MyjsPage} />
</Route>
I am getting issue like:
Warning: You should not use <Route component> and <Route children> in the same route; <Route children> will be ignored
What's the best way of authentication in reactjs?
From what I can see of your React Router Design, you seem to be using React router version 4
In that case you can specify the route in the Component Itself, and make use of withRouter to do a dynamic redirect like
return (<div>
<Router>
<div>
<Route path='/login' component={LoginPage}/>
<EnsureLoggedInContainer/>
</div>
</Router>
</div>
);
and
import React from 'react';
import { connect } from "react-redux";
import {withRouter} from "react-router";
class EnsureLoggedInContainer extends React.Component
{
componentDidMount() {
if ( !this.props.isLoggedIn )
{
this.props.history.push('/login');
}
}
render() {
// console.log(this.props);
if ( this.props.isLoggedIn )
{
return <Route path='/abc' component={abc} />
}
else
{
return null;
}
}
}
const mapStateToProps = (state,ownProps) => {
return{
isLoggedIn : state.isLoggedIn,
// currentURL : this.props
}
}
export default connect(mapStateToProps)(withRouter(EnsureLoggedInContainer));
I have a state 'isLoggedIn' in App Component.
Now, I want to pass this state as props to the child component 'Secret Component'.
<BrowserRouter>
<App>
<Switch>
<Route path='/secret' component={Secret} />
<Route path='/' component={Top} />
</Switch>
</App>
</BrowserRouter>
But, I'm using react-router(ver4.1) like this and can't figure out how to pass the state of App Component as props to its child component.
const childrenWithProps = React.Children.map(this.props.children, (child) => {
console.log(child);
}
);
I know, by doing like this, I can get an access to this.props.children and set additional props to them but since I wrap my components with Router Component, the children of App Component are now Route components, which makes it complicated...
Could anyone please tell me how to do it?
I'm also worried if I'm doing wrong on how to use react-router.
thanks!
index.js(entry point)
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter, Route, Switch } from 'react-router-dom';
import App from './components/App';
import Secret from './components/Secret';
import Top from './components/Top';
ReactDOM.render(
<BrowserRouter>
<App>
<Switch>
<Route path='/secret' component={Secret} />
<Route path='/' component={Top} />
</Switch>
</App>
</BrowserRouter>
,
document.querySelector('.container')
);
App.js
import React, { Component } from 'react';
import NavigationMenu from './NavigationMenu';
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
isLoggedIn: false
};
this.toggleAuthenticationStatus = this.toggleAuthenticationStatus.bind(this);
}
toggleAuthenticationStatus() {
this.setState({
isLoggedIn: !this.state.isLoggedIn
});
}
render() {
//I want to pass this.state.isLoggedIn as props to Secret Component!!!
const childrenWithProps = React.Children.map(this.props.children, (child) => {
console.log(child);
}
);
return (
<div>
<NavigationMenu isLoggedIn={this.state.isLoggedIn} toggleAuthenticationStatus={this.toggleAuthenticationStatus} />
{this.props.children}
</div>
)
}
}
Secret.js
import React, { Component } from 'react';
class Secret extends Component {
constructor(props) {
super(props);
}
componentWillMount() {
if (this.props.isLoggedIn === false) {
this.props.history.push('/');
}
}
componentWillUpdate() {
if (this.props.isLoggedIn === false) {
this.props.history.push('/');
}
}
render() {
return (
<div>
This content is only for our members!
</div>
)
}
}
export default Secret;
In react-router v4 recommended approach is putting nested routes inside the parent component instead of pass those as children (see the basic example of react-router v4). So in your case, I suggest you to simply replace {this.props.children} with Routes with the Switch component and stop passing them as the children of App. Then you can use render method of Route to pass props to the Secret component as usual.
return (
<div>
<NavigationMenu isLoggedIn={this.state.isLoggedIn} toggleAuthenticationStatus={this.toggleAuthenticationStatus} />
<Switch>
<Route path='/secret' render={() => <Secret isLoggedIn={this.state.isLoggedIn}/>)} />
<Route path='/' component={Top} />
</Switch>
</div>
)