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;
Related
I'm trying to pass a function to a a React Router but it gives me an error despite several adjustments. I tried putting the function in the render(), added this before params props, but nothing seems to be working. How do you you pass a function to selective return between a Route and a Redirect tag?
import React, { Component } from 'react';
import { Switch, Route, Redirect } from 'react-router-dom';
import DogList from './DogList';
import DogDetails from './DogDetails';
class Routes extends Component {
constructor(props) {
super(props);
this.getDog = this.getDog.bind(this);
}
getDog() {
let name = props.match.params.name;
let currDog = this.props.dogs.find(
dog => dog.name.toLowerCase() === name.toLowerCase()
);
if(currDog != undefined) {
return <DogDetails {...props} dog={currDog} /> ;
} else {
return <Redirect to="/dogs" />
}
}
render() {
return(
<Switch>
<Route exact path='/dogs' render= {() => <DogList dogs={this.props.dogs} />} />
<Route exact path='/dogs/:name' render={(props) => {this.getDog()}} />
<Redirect to='/dogs' />
</Switch>
);
}
}
export default Routes;
I recommend you to seperate your components because there might be so many routes, so, you might not be able to manage them in one component.
Anyway, in your case please try sending props as a parameter to your function.
You should wrap your switches with BrowserRouter.
import React, { Component } from 'react';
import { Switch, Route, Redirect, BrowserRouter as Router } from 'react-router-dom';
import DogList from './DogList';
import DogDetails from './DogDetails';
class Routes extends Component {
constructor(props) {
super(props);
this.getDog = this.getDog.bind(this);
}
getDog(props) {
const { dogs } = this.props;
let name = props.match.params.name;
let currDog = dogs.find(
dog => dog.name.toLowerCase() === name.toLowerCase()
);
if(currDog != undefined) {
return <DogDetails {...props} dog={currDog} /> ;
} else {
return <Redirect to="/dogs" />
}
}
render() {
const { dogs } = this.props;
return(
<Router>
<Switch>
<Route exact path='/dogs' render= {() => <DogList dogs={dogs} />} />
<Route exact path='/dogs/:name' render={(props) => this.getDog(props)} />
<Redirect to='/dogs' />
</Switch>
</Router>
);
}
}
Keep in mind this react router documentation. It is a good guide to your example; https://reactrouter.com/web/guides/quick-start
A complete example is here; https://codesandbox.io/s/sleepy-ishizaka-n0433?file=/src/App.js
Use this.props not only props
let name = this.props.match.params.name;
I have these components. I want to turn every <House/> into a dynamic url. For example, when accessing in the browser, http://localhost:3000/houses/house/1
I want to appear the House 1.
The other things in the application are working fine. I just want to solve this problem of implementing dynamic routes.
Router Component
import React from 'react';
import { BrowserRouter as Router, Route, NavLink } from 'react-router-dom'
import App from './App'
import Houses from './Houses'
export default props => (
<Router>
<Route exact path='/' render={() => <App />} >
<Route exact path='/houses' render={() => <Houses />} />
</Route>
</Router>
)
Houses Component
import React, { Component } from 'react'
import House from './House'
var data = require('./db.json');
class Houses extends Component {
constructor(props) {
super(props);
this.state = {
currentHouse: []
};
}
componentDidMount() {
this.setState({
currentHouse: data[0]
})
}
render() {
const {currentHouse} = this.state;
return (
<div className="content house">
<ul>
{currentHouse.photos && currentHouse.photos.map((photo, index) => {
return(
<House photo={photo} key={index}/>
)
})}
</ul>
</div>
)
}
}
export default Houses
House Component
import React from 'react';
function House(prop) {
return (
<li><img src={`/images/${prop.photo}`}/></li>
);
}
export default House;
<Route exact path='/houses/:id' render={(props) => <House {...props} />} />
and inside House component retrieve the id:
prop.match.params.id
Ref: https://scotch.io/courses/using-react-router-4/route-params
I am trying to make a PrivateRoute component for react. Here is my higher order component. Can you tell me what is the problem with this.
import React from "react";
import { Route, Redirect } from "react-router-dom";
import { connect } from "react-redux";
export default ({ component: Component, ...rest }) => {
class PrivateRoute extends React.Component {
render() {
console.log("This is private route called");
if (this.props.profile) {
return (
<Route
{...rest}
render={props =>
this.props.profile.loggedIn === true ? (
<Component {...props} />
) : (
<Redirect to="/login" />
)
}
/>
);
}
}
}
const mapStateToProps = state => ({
profile: state.profile
});
return connect(mapStateToProps)(PrivateRoute);
};
Here's how you can accomplish a protected route via a protected route component.
Working example: https://codesandbox.io/s/yqo75n896x
containers/RequireAuth.js
import React from "react";
import { Route, Redirect } from "react-router-dom";
import { connect } from "react-redux";
import ShowPlayerRoster from "../components/ShowPlayerRoster";
import ShowPlayerStats from "../components/ShowPlayerStats";
import Schedule from "../components/Schedule";
const RequireAuth = ({ match: { path }, isAuthenticated }) =>
!isAuthenticated ? (
<Redirect to="/signin" />
) : (
<div>
<Route exact path={`${path}/roster`} component={ShowPlayerRoster} />
<Route path={`${path}/roster/:id`} component={ShowPlayerStats} />
<Route path={`${path}/schedule`} component={Schedule} />
</div>
);
export default connect(state => ({
isAuthenticated: state.auth.isAuthenticated
}))(RequireAuth);
routes/index.js
import React from "react";
import { BrowserRouter, Route, Switch } from "react-router-dom";
import { createStore } from "redux";
import { Provider } from "react-redux";
import Home from "../components/Home";
import Header from "../containers/Header";
import Info from "../components/Info";
import Sponsors from "../components/Sponsors";
import Signin from "../containers/Signin";
import RequireAuth from "../containers/RequireAuth";
import rootReducer from "../reducers";
const store = createStore(rootReducer);
export default () => (
<Provider store={store}>
<BrowserRouter>
<div>
<Header />
<Switch>
<Route exact path="/" component={Home} />
<Route path="/info" component={Info} />
<Route path="/sponsors" component={Sponsors} />
<Route path="/protected" component={RequireAuth} />
<Route path="/signin" component={Signin} />
</Switch>
</div>
</BrowserRouter>
</Provider>
);
Or, if you want something that wraps all routes (instead of having to specify a protected route component). Then you can do something like the below.
Working example: https://codesandbox.io/s/5m2690nn6n
components/RequireAuth.js
import React, { Component, Fragment } from "react";
import { withRouter } from "react-router-dom";
import Login from "./Login";
import Header from "./Header";
class RequireAuth extends Component {
state = { isAuthenticated: false };
componentDidMount = () => {
if (!this.state.isAuthenticated) {
this.props.history.push("/");
}
};
componentDidUpdate = (prevProps, prevState) => {
if (
this.props.location.pathname !== prevProps.location.pathname &&
!this.state.isAuthenticated
) {
this.props.history.push("/");
}
};
isAuthed = () => this.setState({ isAuthenticated: true });
unAuth = () => this.setState({ isAuthenticated: false });
render = () =>
!this.state.isAuthenticated ? (
<Login isAuthed={this.isAuthed} />
) : (
<Fragment>
<Header unAuth={this.unAuth} />
{this.props.children}
</Fragment>
);
}
export default withRouter(RequireAuth);
routes/index.js
import React from "react";
import { BrowserRouter, Switch, Route } from "react-router-dom";
import Home from "../components/Home";
import Players from "../components/Players";
import Schedule from "../components/Schedule";
import RequireAuth from "../components/RequireAuth";
export default () => (
<BrowserRouter>
<RequireAuth>
<Switch>
<Route exact path="/" component={Home} />
<Route exact path="/players" component={Players} />
<Route path="/schedule" component={Schedule} />
</Switch>
</RequireAuth>
</BrowserRouter>
);
Or, if you want something a bit more modular, where you can pick and choose any route, then you can create a wrapper HOC. See this example (while it's written for v3 and not for authentication, it's still the same concept).
It looks like your render function's only return is inside of an if block, so its returning null. You need to fix the logic to just return the Route and make profile a required prop in your proptypes check instead of using an if block.
PrivateRoute.propTypes = {
profile: PropTypes.object.isRequired
};
My problem is when I change a state inside a redux store and based on this state I mount or unmount a component. The Code looks like this:
class Main extends Component {
render() {
const { dropdownState } = this.props;
return (
<div>
<SecondHeadBar />
<div className="main">
<Switch>
<Route exact path='/' component={withRouter(WebsiteIndex)}/>
<Route path='/track/:trackid' component={withRouter(MssTrack)}/>
<Route path='/album/:albumid' component={withRouter(Container.AlbumContainer)}/>
<Route path='/profile/:userName' component={withRouter(MssUser)}/>
<Route path='/upload/:albumid' component={withRouter(MssUploadTemplate)}/>
<Route path='/upload' component={withRouter(MssUploadTemplate)}/>
<Route path='/admin' component={withRouter(ControlCenter)}/>
<Route path='/kategorie' component={withRouter(Category)} exact/>
<Route path='/kategorie/:catName' component={withRouter(Folder)}/>
<Route path='/notFound' component={withRouter(NotFound)}/>
<Route path='/meine-eintraege' component={withRouter(Container.MyEntriesContainer)}/>
</Switch>
</div>
{dropdownState ? <DownloadDropdown /> : ''}
</div>
);
}
}
function mapStateToProps(state) {
return {
dropdownState: state.collection.dropdownState
};
}
function mapDispatchToProps(dispatch) {
return {
dispatch
};
}
export default connect(mapStateToProps, mapDispatchToProps)(Main);
Whenever the prop dropdownState changes. And the Component DownloadDropdown gets mounted then everything in the Main Component gets rerendered. So the content flashes.
Simplest solution would be to have <DownloadDropdown /> be a container component that is connected to Redux and will always stay mounted although not visible. Then you can utilize a HOC or something that's always mounted and visible (like <SecondHeadBar />) and have it connected to a Redux action creator that toggles DownloadDropdown's visiblity. In other words, isolate Redux to two components, instead of over your entire route tree.
Working example: https://codesandbox.io/s/yw4m7yz8r1 (navigate around the routes and click the 'Download Schedule' link at the top!)
I'm not sure how you are triggering the mount/unmount, but let's stay it's being toggled by a button:
SecondHeadBar.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { handleDropdown } from '../actions';
class SecondHeadBar extends Component {
state = {...}
componentDidMount = () => { ... }
render = () => (
<div>
...
<button onClick={this.props.handleDropdown}>Toggle Dropdown</button>
...
</div>
)
}
export default connect(null, { handleDropdown })(SecondHeadBar)
DownloadDropdown.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
class DownloadDropdown extends Component {
state = { ... }
componentDidMount = () => { ... }
render = () => (
this.props.isVisible
? <div>I'm visible!</div>
: null
)
}
export default connect(state => ({ isVisible: state.dropdown }))(DownloadDropdown)
actions.js
import { TOGGLE_DROPDOWN } from '../types'
export const handleDropdown = () => ({
type: TOGGLE_DROPDOWN
})
reducers.js
import { TOGGLE_DOWN } from '../types';
...
const dropdownReducer = (state=false, { type, payload }) => {
switch(type) {
case TOGGLE_DROPDOWN: return !state
default: return state
}
}
export default = combineReducer({
...
dropdown: dropdownReducer
...
})
routes.js
const Main = () => (
<div>
<SecondHeadBar />
<div className="main">
<Switch>
<Route exact path='/' component={withRouter(WebsiteIndex)}/>
<Route path='/track/:trackid' component={withRouter(MssTrack)}/>
<Route path='/album/:albumid' component={withRouter(Container.AlbumContainer)}/>
<Route path='/profile/:userName' component={withRouter(MssUser)}/>
<Route path='/upload/:albumid' component={withRouter(MssUploadTemplate)}/>
<Route path='/upload' component={withRouter(MssUploadTemplate)}/>
<Route path='/admin' component={withRouter(ControlCenter)}/>
<Route path='/kategorie' component={withRouter(Category)} exact/>
<Route path='/kategorie/:catName' component={withRouter(Folder)}/>
<Route path='/notFound' component={withRouter(NotFound)}/>
<Route path='/meine-eintraege' component={withRouter(Container.MyEntriesContainer)}/>
</Switch>
</div>
<DownloadDropdown/>
</div>
);
export default Main;
Now, when the user clicks the "Toggle Dropdown" button in <SecondHeadBar/>, it'll update <DownloadDropdown/>'s visibility without affecting your route tree.
I think you can use this lifecycle methods to check.
static getDerivedStateFromProps(nextProps, prevState) {
if (nextProps.name !== prevState.name) {
return { name: nextProps.name};
}
}
or for older version check in componentwillreceiveProps and stops re render.
I'm using i18-next library to switch languages in my app. So it's done without reloading a page. The language switch is done via:
render() {
const toggle = lng => i18n.changeLanguage(lng);
return (
<a href="javascript:void(0)" onClick={()=>{ toggle();}}></a>
)
I'd make such functionality: once the language is switched add to a URL a language param. So once change is occured it should looks like: www.website.com/xx
I've read mostly all topics regarding Rect-Router v4 and history but all suggestions didn't work in my project. Some of them are related to obsolete functionality. I've also tried few example with withRouter, but nothing worked...
How it could be achieved in my case?
index.js:
import { Router, Route, Switch } from 'react-router-dom';
import createBrowserHistory from 'history/createBrowserHistory';
const customHistory = createBrowserHistory();
...
return (
<I18nextProvider i18n={i18n}>
<div>
<Router history={customHistory}>
<Switch>
<Route exact path="/:lng?" component={Page} />
<Route path={`/:lng?/someURL`} component={Page}/>
...
<Route component={NoMatch} />
</Switch>
</Router>
<ModalContainer />
</div>
</I18nextProvider>
)
navigation component:
handleClick() {
**append URL with lang param**
console.log(history);
-> history: History { length: 1, scrollRestoration: "auto", state: null }
history.push('/redirected');
-> TypeError: history.push is not a function
}
render() {
const toggle = lng => i18n.changeLanguage(lng);
return (
<a href="javascript:void(0)" onClick={()=>{ toggle(this.props.event); this.handleClick(); }}></a>
)
Should it be done with function as handleClick() or this event should be global? The languages are switched from several components.
React-Router V4.2.0
Your navigation component needs to use a Link or NavLink component from react-router. There is no need for you manually access the router from context.
import {NavLink} from 'react-router-dom';
class NavComponent extends React.Component {
render() {
const { i18n } = this.props;
const toggle = lng => i18n.changeLanguage(lng);
if (this.props.event) {
return (
<li><NavLink className={(this.props.spanClassName)} onClick={()=> toggle(this.props.event)} to={this.props.event}/></li>
);
}
else
return null;
}
};
well, I came to this solution. Maybe it will be helpful for some people.
If there are more better ways to achieve it, please post your answer.
But one problem left. Once I'm in www.website.com/xx/someURL and press language switch the xx part of the URL should be swapped with new param but someURL should remain. Anybody knows how to make it?
index.js:
import { Router, Route, Switch } from 'react-router-dom';
import createBrowserHistory from 'history/createBrowserHistory';
const customHistory = createBrowserHistory();
render() {
return (
<I18nextProvider i18n={i18n}>
<div>
<Router history={customHistory}>
<Switch>
<Route exact path="/:lng?" component={Page} />
<Route path={`/:lng?/someURL`} component={Page}/>
<Route component={NoMatch} />
</Switch>
</Router>
<ModalContainer />
</div>
</I18nextProvider>
)
}
nav component:
import PropTypes from "prop-types";
class NavLink extends React.Component {
static contextTypes = {
router: PropTypes.object
}
constructor(props, context) {
super(props, context);
}
handleClick(path) {
this.context.router.history.push(path);
}
render() {
const { i18n } = this.props;
const toggle = lng => i18n.changeLanguage(lng);
if (this.props.event) {
return (
<li><a href="javascript:void(0)" onClick={()=>{ toggle(this.props.event); this.handleClick(this.props.event); }}><span className={(this.props.spanClassName)}></span></a></li>
)
}
}
};