component 1 passing the state through link's to
<Link to={{
pathname: "/Dashboard",
state: { color: "red" }
}}>
component 2 receiving the state
import React from 'react';
export interface DashboardProps {
location: any;
}
export interface DashboardState {
}
class Dashboard extends React.Component<DashboardProps, DashboardState> {
render() {
console.log("props", this.props.location);
return (
<h1>dashboard</h1>
);
}
}
export default Dashboard;
result
main component render
public render() {
return (
<HashRouter>
<div>
<Route path="/Dashboard" render={(props: any) => (<Dashboard {...props} />)} />
</div>
</HashRouter >
)
}
I believe I don't need to wrap my dashboard(component to receive state through link react router dom)
with "withRouter"
what a pity ! HashRouter doesn't support location.state:
You can see here https://github.com/ReactTraining/react-router/issues/7628 or https://github.com/ReactTraining/react-router/blob/master/packages/react-router-dom/docs/api/HashRouter.md
Hashrouter doesn't support location.key and location.state. Go through the documentation here https://v5.reactrouter.com/web/api/HashRouter
Related
I have a problem with the code - I do not understand how to solve it - I tried for a few hours - it does not work.
I do not know what's going on. I'm just trying to access from a component of courses - and a component of a single course.
I get this error:
This is the code I wrote down so far - not a long code.
course.js:
import React, { Component } from 'react';
class Course extends Component {
render() {
return (
<div>
<h1>this.props.location.title</h1>
<p>You selected the Course with ID: {this.props.match.params.id}</p>
</div>
);
}
}
export default Course;
courses.js:
import React, { Component } from 'react';
import './Courses.css';
import { Link } from "react-router-dom";
class Courses extends Component {
state = {
courses: [
{ id: 1, title: 'Angular - The Complete Guide' },
{ id: 2, title: 'Vue - The Complete Guide' },
{ id: 3, title: 'PWA - The Complete Guide' }
]
}
render() {
return (
<div>
<h1>Amazing Udemy Courses</h1>
<section className="Courses">
{
this.state.courses.map(course => {
return (
<Link
key={course.id}
to={{
pathname: this.props.match.url + "/" + course.id,
title: course.title
}}>
<article className="Course">{course.title}</article>
</Link>
)
})
}
</section>
</div>
);
}
}
export default Courses;
app.js:
import React, { Component } from 'react';
import Courses from './containers/Courses/Courses';
import Users from './containers/Users/Users';
import { NavLink, BrowserRouter, Switch, Route } from "react-router-dom";
import './App.css';
class App extends Component {
render() {
return (
<BrowserRouter>
<div className="App">
<header>
<nav>
<ul>
<li>
<NavLink
to="/users"
exact
activeClassName="my-active"
activeStyle={{
color: '#fa923f',
textDecoration: 'underline'
}}>Users
</NavLink>
</li>
<li>
<NavLink to={{
pathname: '/courses',
hash: '#submit',
search: '?quick-submit=true'
}}>Courses
</NavLink>
</li>
</ul>
</nav>
</header>
<Switch>
<Route exact path="/">
</Route>
<Route path="/users">
<Users />
</Route>
<Route path="/courses">
<Courses />
</Route>
</Switch>
<ol style={{ textAlign: 'left' }}>
<li>Pass the course ID to the "Course" page and output it there</li>
<li>Pass the course title to the "Course" page - pass it as a param or score bonus points by passing it as query params (you need to manually parse them though!)</li>
<li>Load the "Course" component as a nested component of "Courses"</li>
<li>Add a 404 error page and render it for any unknown routes</li>
<li>Redirect requests to /all-courses to /courses (=> Your "Courses" page)</li>
</ol>
</div>
</BrowserRouter>
);
}
}
export default App;
You're trying to access the props that come from react router, with the syntax that you're using on your app.js file you're not passing any props. To be able to pass react router props to your component you can use this syntax instead:
<Route path="/courses" component={Courses} />
So everytime you access a link that takes you to the courses you will have available in you component all the props that are coming from react router.
Here's a sandbox with an example of a component that uses that syntax to obtain all the router props vs another that uses the syntax you're using and it does not have access to the router props:
Sandbox with example
I'm using functional components but with your example doing this.props will give the router props including the match object. With your courses component being a class I think this is the easiest way to do it, you can also wrap the component in a withRouter HOC to accomplish the same thing:
WIth router docs
You need to set props in your child components for them to be available, like so...
<Courses
match={this.props.match}
/>
Right now, all you have is <Courses />, without passing an props. So in the Courses class, of course you'll get this message: Cannot read property 'url' of undefined on your call of <Link ....this.props.match.....>.
But if you pass along the match prop, like match={this.props.match}, there should no longer be this error.
Take a look at the ReactJS documentation on how props work...
However, elements can also represent user-defined components:
const element = <Welcome name="Sara" />;
When React sees an element representing a user-defined component, it passes JSX attributes and children to this component as a single object. We call this object “props”.
Source: ReactJS.org: Components and Props
In React i have my App.js page where i keep my states. I'm importing user1.js component to App.js, and in user1.js component i have a link button that takes me to path /user2.
When i click the button, React will set state property called testValue to true and in user2.js page ternary operator should choose the first value - test works because of that. But for some reason it does not work.
Any help?
APP.JS
import React, { Component } from 'react';
import './App.css';
import User1 from './components/user1';
class App extends Component {
constructor(props){
super(props);
this.state = {
testValue:false
};
}
change = () => {
this.setState({
testValue:true
},() => {
console.log(this.state.testValue)
});
}
render() {
return (
<div className="App">
<User1 change={this.change}/>
</div>
);
}
}
export default App;
USER1.JS
import React from 'react';
import { BrowserRouter, Route, Switch, Link } from 'react-router-dom';
import User2 from './user2.js';
const User1 = (props) => {
return(
<BrowserRouter>
<div>
<Link to ="/user2">
<button onClick={props.change}>Next page</button>
</Link>
<Switch>
<Route path="/user2" exact component={User2}/>
</Switch>
</div>
</BrowserRouter>
); // end of return
};
export default User1;
USER2.JS
import React from 'react';
const User2 = (props) => {
console.log(props)
return(
<div>
{props.testValue ?
<p>test works</p>
:
<p>test does not work</p>
}
</div>
);
};
export default User2;
This is what i expected - test works
This is what i got - test does not work
You want to pass a custom property through to a component rendered via a route. Recommended way to do that is to use the render method.
<Route path="/user2" exact render={(props) => <User2 {...props} testValue={true} />} />
I think a valid inquiry here would be what are you wanting to pass through as an extra prop? whats the use case here? You may be trying to pass data in a way you shouldn't (context would be nice :D).
i am having route where i pass id,but i dont want to show id in url,
`<Route path={`${match.url}invite-members/:groupID`} exact component={InviteMembers} />`
this gets converted in url https://local..../invite-members/5,
but instead of that i want https://local..../invite-members, but the functionality should remain the same as in i get id in invite-members through this.props.match.params.groupID should be as it is,please help
using react router "react-router-dom": "^4.2.2",
If you want to change url to '/invite-members', you can add the Redirect component. And in case you want to save groupId, you could save it to your component state:
import React, { PureComponent } from "react";
import PropTypes from "prop-types";
import {
Router,
Route,
Link,
Switch,
Redirect
} from "react-router-dom";
class Root extends PureComponent {
// add groupId field to your component
// In case you use redux or any another state management library, you can save groupId to store
state = { groupId: null };
render() {
const { store, history } = this.props;
// just for example I defined '/' path to redirect on /invite-members url
return (
<Router>
<Switch>
<Route
path="/"
exact
render={props => (
<Redirect
to={{
pathname: "/invite-members/123",
state: { from: props.location }
}}
/>
)}
/>
<Route
path="/invite-members"
exact
render={props => (
<InviteMembers {...props} groupId={this.state.groupId} />
)}
/>
<Route
path="/invite-members/:groupID"
exact
render={props => {
return (
<RedirectAndSaveGroupId
{...props}
groupId={props.match.params.groupID}
onSetGroupId={groupId => {
this.setState({ groupId });
}}
/>
);
}}
/>
</Switch>
</Router>
);
}
}
export default Root;
class RedirectAndSaveGroupId extends PureComponent {
componentDidMount() {
// save groupId to Root component
this.props.onSetGroupId(this.props.groupId);
}
render() {
// redirect to /invite-members without groupId
return (
<Redirect
to={{
pathname: "/invite-members",
state: { from: this.props.location }
}}
/>
);
}
}
// Just for demo. In this.props.groupId we can receive groupId
class InviteMembers extends PureComponent {
render() {
return this.props.groupId;
}
}
Note, that in case you using any state management library such as Redux, you can store group id in them
I maybe have a very simple solution :
Router link :
<Link to={{pathname: '/item/'+name, state : {id}}}>{name}</Link>
In the Targeted file :
state = this.props.location.state
QueryParameters = () => {
const id = this.state.id
return { id }
}
And launch your query requiring the ID. It does not appear in the url.
Passing data in the params object will always result in that data being shown in the URL. Because the params object is built from the url.
How should I redirect an user to a different url when I cannot get an access to history props of react-router?
What I want to do is when an user clicks an log-out link on the navigation menu, the user get redirected to the root path '/'.
handleAuthentication(event) {
this.props.toggleAuthenticationStatus(() => {
// I want to redirect an user to the root path '/' in this callback function.
});
}
handleAuthentication method is called when an user clicks an login/logout link on the navigation menu.
toggleAuthenticationStatus(callback) {
this.setState((prevState, props) => {
return { isLoggedIn: !prevState.isLoggedIn }
},
callback()
);
}
Then, when handleAuthentication method in the NavigationMenu Component, it calls toggleAuthenticationStatus method in App Component that changes the state of Login/Logout and run callback function which is defined in the handleAuthentication method in the NavigationMenu Component.
Is it ok to run "window.location.href = '/'" directly?
Does it mess up the react-router history object???
Could anyone please how I should implement user redirect in a right way?
App Component
import React, { Component } from 'react';
import NavigationMenu from './NavigationMenu';
import { BrowserRouter, Route, Switch } from 'react-router-dom';
import Secret from './Secret';
import Top from './Top';
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
isLoggedIn: false
};
this.toggleAuthenticationStatus = this.toggleAuthenticationStatus.bind(this);
}
toggleAuthenticationStatus(callback) {
this.setState((prevState, props) => {
return { isLoggedIn: !prevState.isLoggedIn }
},
callback()
);
}
render() {
return (
<BrowserRouter>
<div>
<NavigationMenu isLoggedIn={this.state.isLoggedIn} toggleAuthenticationStatus={this.toggleAuthenticationStatus} />
<Switch>
<Route path='/secret' render={(props) => <Secret isLoggedIn={this.state.isLoggedIn} {...props} />} />
<Route path='/' component={Top} />
</Switch>
</div>
</BrowserRouter>
)
}
}
NavigationMenu Component
import React, { Component } from 'react';
import { Link } from 'react-router-dom';
class NavigationMenu extends Component {
constructor(props) {
super(props);
this.handleAuthentication = this.handleAuthentication.bind(this);
}
handleAuthentication(event) {
this.props.toggleAuthenticationStatus(() => {
// I want to redirect an user to the root path '/' in this callback function.
});
}
render() {
return (
<ul>
<li><Link to='/'>Top</Link></li>
<li><Link to='/secret'>Secret</Link></li>
<li><Link to='/login' onClick={this.handleAuthentication}>
{this.props.isLoggedIn === true ? 'Logout' : 'Login'}
</Link></li>
</ul>
)
}
}
export default NavigationMenu;
I found the method 'withRouter' in react-router.
This seems the solution in my situation.
I'm going to try using it.
https://reacttraining.com/react-router/web/api/withRouter
You can get access to the history object’s properties and the closest
's match via the withRouter higher-order component. withRouter
will re-render its component every time the route changes with the
same props as render props: { match, location, history }.
This is driving me crazy. When I try to use React Router's Link within a nested route, the link updates in the browser but the view isn't changing. Yet if I refresh the page to the link, it does. Somehow, the component isn't updating when it should (or at least that's the goal).
Here's what my links look like (prev/next-item are really vars):
<Link to={'/portfolio/previous-item'}>
<button className="button button-xs">Previous</button>
</Link>
<Link to={'/portfolio/next-item'}>
<button className="button button-xs">Next</button>
</Link>
A hacky solution is to manaully call a forceUpate() like:
<Link onClick={this.forceUpdate} to={'/portfolio/next-item'}>
<button className="button button-xs">Next</button>
</Link>
That works, but causes a full page refresh, which I don't want and an error:
ReactComponent.js:85 Uncaught TypeError: Cannot read property 'enqueueForceUpdate' of undefined
I've searched high and low for an answer and the closest I could come is this: https://github.com/reactjs/react-router/issues/880. But it's old and I'm not using the pure render mixin.
Here are my relevant routes:
<Route component={App}>
<Route path='/' component={Home}>
<Route path="/index:hashRoute" component={Home} />
</Route>
<Route path="/portfolio" component={PortfolioDetail} >
<Route path="/portfolio/:slug" component={PortfolioItemDetail} />
</Route>
<Route path="*" component={NoMatch} />
</Route>
For whatever reason, calling Link is not causing the component to remount which needs to happen in order to fetch the content for the new view. It does call componentDidUpdate, and I'm sure I could check for a url slug change and then trigger my ajax call/view update there, but it seems like this shouldn't be needed.
EDIT (more of the relevant code):
PortfolioDetail.js
import React, {Component} from 'react';
import { browserHistory } from 'react-router'
import {connect} from 'react-redux';
import Loader from '../components/common/loader';
import PortfolioItemDetail from '../components/portfolio-detail/portfolioItemDetail';
import * as portfolioActions from '../actions/portfolio';
export default class PortfolioDetail extends Component {
static readyOnActions(dispatch, params) {
// this action fires when rendering on the server then again with each componentDidMount.
// but not firing with Link...
return Promise.all([
dispatch(portfolioActions.fetchPortfolioDetailIfNeeded(params.slug))
]);
}
componentDidMount() {
// react-router Link is not causing this event to fire
const {dispatch, params} = this.props;
PortfolioDetail.readyOnActions(dispatch, params);
}
componentWillUnmount() {
// react-router Link is not causing this event to fire
this.props.dispatch(portfolioActions.resetPortfolioDetail());
}
renderPortfolioItemDetail(browserHistory) {
const {DetailReadyState, item} = this.props.portfolio;
if (DetailReadyState === 'WORK_DETAIL_FETCHING') {
return <Loader />;
} else if (DetailReadyState === 'WORK_DETAIL_FETCHED') {
return <PortfolioItemDetail />; // used to have this as this.props.children when the route was nested
} else if (DetailReadyState === 'WORK_DETAIL_FETCH_FAILED') {
browserHistory.push('/not-found');
}
}
render() {
return (
<div id="interior-page">
{this.renderPortfolioItemDetail(browserHistory)}
</div>
);
}
}
function mapStateToProps(state) {
return {
portfolio: state.portfolio
};
}
function mapDispatchToProps(dispatch) {
return {
dispatch: dispatch
}
}
export default connect(mapStateToProps, mapDispatchToProps)(PortfolioDetail);
PortfolioItemDetail.js
import React, {Component} from 'react';
import {connect} from 'react-redux';
import Gallery from './gallery';
export default class PortfolioItemDetail extends React.Component {
makeGallery(gallery) {
if (gallery) {
return gallery
.split('|')
.map((image, i) => {
return <li key={i}><img src={'/images/portfolio/' + image} alt="" /></li>
})
}
}
render() {
const { item } = this.props.portfolio;
return (
<div className="portfolio-detail container-fluid">
<Gallery
makeGallery={this.makeGallery.bind(this)}
item={item}
/>
</div>
);
}
}
function mapStateToProps(state) {
return {
portfolio: state.portfolio
};
}
export default connect(mapStateToProps)(PortfolioItemDetail);
gallery.js
import React, { Component } from 'react';
import { Link } from 'react-router';
const Gallery = (props) => {
const {gallery, prev, next} = props.item;
const prevButton = prev ? <Link to={'/portfolio/' + prev}><button className="button button-xs">Previous</button></Link> : '';
const nextButton = next ? <Link to={'/portfolio/' + next}><button className="button button-xs">Next</button></Link> : '';
return (
<div>
<ul className="gallery">
{props.makeGallery(gallery)}
</ul>
<div className="next-prev-btns">
{prevButton}
{nextButton}
</div>
</div>
);
};
export default Gallery;
New routes, based on Anoop's suggestion:
<Route component={App}>
<Route path='/' component={Home}>
<Route path="/index:hashRoute" component={Home} />
</Route>
<Route path="/portfolio/:slug" component={PortfolioDetail} />
<Route path="*" component={NoMatch} />
</Route>
Could not get to the bottom of this, but I was able to achieve my goals with ComponentWillRecieveProps:
componentWillReceiveProps(nextProps){
if (nextProps.params.slug !== this.props.params.slug) {
const {dispatch, params} = nextProps;
PortfolioDetail.readyOnActions(dispatch, params, true);
}
}
In other words, for whatever reason when I use React Router Link to link to a page with the SAME PARENT COMPONENT, it doesn't fire componentWillUnMount/componentWillMount. So I'm having to manually trigger my actions. It does work as I expect whenever I link to Routes with a different parent component.
Maybe this is as designed, but it doesn't seem right and isn't intuitive. I've noticed that there are many similar questions on Stackoverflow about Link changing the url but not updating the page so I'm not the only one. If anyone has any insight on this I would still love to hear it!
It's good to share the components code also. However, I tried to recreate the same locally and is working fine for me. Below is the sample code,
import { Route, Link } from 'react-router';
import React from 'react';
import App from '../components/App';
const Home = ({ children }) => (
<div>
Hello There Team!!!
{children}
</div>
);
const PortfolioDetail = () => (
<div>
<Link to={'/portfolio/previous-item'}>
<button className="button button-xs">Previous</button>
</Link>
<Link to={'/portfolio/next-item'}>
<button className="button button-xs">Next</button>
</Link>
</div>
);
const PortfolioItemDetail = () => (
<div>PortfolioItemDetail</div>
);
const NoMatch = () => (
<div>404</div>
);
module.exports = (
<Route path="/" component={Home}>
<Route path='/' component={Home}>
<Route path="/index:hashRoute" component={Home} />
</Route>
<Route path="/portfolio" component={PortfolioDetail} />
<Route path="/portfolio/:slug" component={PortfolioItemDetail} />
<Route path="*" component={NoMatch} />
</Route>
);
componentWillReceiveProps is the answer to this one, but it's a little annoying. I wrote a BaseController "concept" which sets a state action on route changes EVEN though the route's component is the same. So imagine your routes look like this:
<Route path="test" name="test" component={TestController} />
<Route path="test/edit(/:id)" name="test" component={TestController} />
<Route path="test/anything" name="test" component={TestController} />
So then a BaseController would check the route update:
import React from "react";
/**
* conceptual experiment
* to adapt a controller/action sort of approach
*/
export default class BaseController extends React.Component {
/**
* setState function as a call back to be set from
* every inheriting instance
*
* #param setStateCallback
*/
init(setStateCallback) {
this.setStateCall = setStateCallback
this.setStateCall({action: this.getActionFromPath(this.props.location.pathname)})
}
componentWillReceiveProps(nextProps) {
if (nextProps.location.pathname != this.props.location.pathname) {
this.setStateCall({action: this.getActionFromPath(nextProps.location.pathname)})
}
}
getActionFromPath(path) {
let split = path.split('/')
if(split.length == 3 && split[2].length > 0) {
return split[2]
} else {
return 'index'
}
}
render() {
return null
}
}
You can then inherit from that one:
import React from "react";
import BaseController from './BaseController'
export default class TestController extends BaseController {
componentWillMount() {
/**
* convention is to call init to
* pass the setState function
*/
this.init(this.setState)
}
componentDidUpdate(){
/**
* state change due to route change
*/
console.log(this.state)
}
getContent(){
switch(this.state.action) {
case 'index':
return <span> Index action </span>
case 'anything':
return <span>Anything action route</span>
case 'edit':
return <span>Edit action route</span>
default:
return <span>404 I guess</span>
}
}
render() {
return (<div>
<h1>Test page</h1>
<p>
{this.getContent()}
</p>
</div>)
}
}
I got stuck on this also in React 16.
My solution was as follows:
componentWillMount() {
const { id } = this.props.match.params;
this.props.fetchCategory(id); // Fetch data and set state
}
componentWillReceiveProps(nextProps) {
const { id } = nextProps.match.params;
const { category } = nextProps;
if(!category) {
this.props.fetchCategory(id); // Fetch data and set state
}
}
I am using redux to manage state but the concept is the same I think.
Set the state as per normal on the WillMount method and when the WillReceiveProps is called you can check if the state has been updated if it hasn't you can recall the method that sets your state, this should re-render your component.
I am uncertain whether it fixes the original problem, but I had a similar issue which was resolved by passing in the function callback () => this.forceUpdate() instead of this.forceUpdate.
Since no one else is mentioning it, I see that you are using onClick={this.forceUpdate}, and would try onClick={() => this.forceUpdate()}.
Try to import BrowserRouter instead of Router
import { Switch, Route, BrowserRouter as Router } from 'react-router-dom;
It worked for me after spending a couple of hours solving this issue.
I solved this by building '' custom component instead of '', and inside it I use in the method instead of :
import * as React from "react";
import {Navigate} from "react-router-dom";
import {useState} from "react";
export function ReactLink(props) {
const [navigate, setNavigate] = useState(<span/>);
return (
<div style={{cursor: "pointer"}}
onClick={() => setNavigate(<Navigate to={props.to}/>)}>
{navigate}
{props.children}
</div>
}