React Router, access history object from child components - reactjs

I'd like to listen to location change using history.listen in one of deep nested child component.
Should the top parent component which has this.props.history pass down the props all the way down to the child components?
I'm using 2.8.1 react-router
I am willing to upgrade to newer version if it allows me to do this easily.

import { withRouter } from 'react-router-dom';
class MyComponent extends React.Component {
render() {
// props also has match, location
const { history } = this.props;
...
}
}
export default withRouter(MyComponent);

You could get history by getting router context.
import React, {PropTypes} from 'react';
class myComponent extends React.Component {
render() {
console.log(this.context.history);
}
}
myComponent.contextTypes = {
history: PropTypes.object
}
export default myComponent;

Related

How to include the Match object into a Reacts component class?

I am using react router v5, and trying to get URL parameters by props match object into my react component class. However it is not working! What am I doing wrong here?
When I create my component as a JavaScript function it all works fine, but when I try to create my component as a JavaScript class it doesn't work.
Perhaps I am doing something wrong? How do I pass the Match object in to my class component and then use that to set my component's state?
here code:
import React, { Component } from 'react';
import { Link} from "react-router-dom";
export class JobDetail extends Component {
state = {
// jobsData: [],
}
async componentDidMount() {
const props = this.props.match;
console.log("---props data---",props); // it's showing undefined
}
render() {
return (
<>
test message
</>
)
}
}
export default JobDetail

PubSub not working within route components

I was trying to pass data within route components. Since the react-router-dom remove props for class components in the latest version (v6), I just imported pubsub.js and tried to pass the data through the Link with an onclick event.
Here is the component waiting for publishing (showing part of codes).
import React, { Component } from 'react'
import { Link } from 'react-router-dom'
import PubSub from 'pubsub-js';
export default class ProductDetails extends Component {
state = {
product: {}
}
componentDidMount() {
PubSub.subscribe('product', (_, product) => {
this.setState({product})
})
}
render(){...}
}
Here is the component passing the data:
import React, { Component } from 'react'
import { Link } from 'react-router-dom';
import PubSub from 'pubsub-js';
export default class ProductHome extends Component {
pubProduct = (product) => {
return () => {
PubSub.publish('product', product)
}
}
...
render(){
const product = {a: 1}
<Link
to='/product/details'
onClick={this.pubProduct(product)}
> Details </Link>
...
}
Both of them are route components. I also tried publishSync but still not working. After click the Link, the state in the ProductDetails component did not change.
If router doesn't support pubsub, how to pass data then? I know using hook apis in react-router-dom v6 could be the best way to handle this kind of problem. But for class components, is there a good way to pass any data within route components in v6?
Thanks!

How to access state of one component into another component in react

import React, { Component } from 'react';
class one extends React.Component
{
constructor()
{
super();
this.state = {
number:26
}
}
render()
{
return(
<div></div>
);
}
}
export default one;
import React, { Component } from 'react';
import one from './one'
class HomePage extends React.Component
{
render()
{
return(
<div>{one.state.number}</div>
);
}
}
export default HomePage;
is it possible to access number state
is there any way to access state of one component into another component?
please suggest me if any solution is present.
As Shubam has explained it, Though I would like to form it as a complete answer
First of all, I would like to let you know that Never Use lowercase letters to name your React Components.So name your component to One instead of one.
Now Comming back to your question:-
No This is not Possible, If your app contains few components then it's better to pass the state object as the props, But if your app contains too many components then better to use predictable state containers like Redux or Flux rather than passing state as props.
So you may apply these changes and I hope You will get What You Desire:-
One Component:-
import React, { Component } from 'react';
import Homepage from './homepage';
class One extends React.Component
{
constructor()
{
super();
this.state = {
number:26
}
}
render()
{
return(
<Homepage data={this.state}/>
);
}
}
export default One;
Homepage Component:-
import React, { Component } from 'react';
class Homepage extends React.Component
{
render()
{
console.log("this is homepage",this.props);
return(
<div>{this.props.data.number}</div>
);
}
}
export default Homepage;
Please Raise Your doubts if any, Or if you find any error in it.

WebStorm highlights React component when props passed through react-redux connect function

I have a container component:
import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
class MyComponent extends Component {
static propTypes = {
someEntities: PropTypes.object.isRequired
}
....
}
export default connect(state => ({ someEntities: state.someEntities })(MyComponent)
So I am passing props via connect to this component, but when I place component in code, like:
....
import MyComponent from './MyComponent';
....
<div><MyComponent /></div>
....
WebStorm highlights MyComponent and gives me an error: Element MyComponent doesn't have required attribute someEntities.
I am using version 2016.3.1. Is this an error? Should I use propTypes in this case?
This is a known issue, tracked as WEB-21692; please follow it for updates (https://intellij-support.jetbrains.com/hc/en-us/articles/207241135-How-to-follow-YouTrack-issues-and-receive-notifications)

Navigating Programmatically in React-Router v4

I couldn't wait and I jumped into using the latest alpha version of react-router v4. The all-new <BrowserRouter/> is great in keeping your UI in sync with the browser history, but how do I use it to navigate programmatically?
The router will add a history object to your component in the props hash. So in your component, simply do:
this.props.history.push('/mypath')
Here is a full example:
In App.js:
import React from 'react'
import {BrowserRouter as Router, Route} from 'react-router-dom'
import Login from './Login'
export default class App extends React.Component {
render() {
return (
<Router>
<div>
<Route exact path='/login' component={Login} />
</div>
</Router>
)
}
}
In Login.js:
import React, {PropTypes} from 'react'
export default class Login extends React.Component {
constructor(props) {
super(props)
this.handleLogin = this.handleLogin.bind(this)
}
handleLogin(event) {
event.preventDefault()
// do some login logic here, and if successful:
this.props.history.push(`/mypath`)
}
render() {
return (
<div>
<form onSubmit={this.handleLogin}>
<input type='submit' value='Login' />
</form>
</div>
)
}
}
In the past you might have used browserHistory to push a new path. This won't work with react-router v4. Instead you have make use of React's context and router's transitionTo method.
Here's a simple example:
import React from 'react';
class NavigateNext extends React.Component {
constructor() {
super();
this.navigateProgramatically = this.navigateProgramatically.bind(this);
}
navigateProgramatically(e) {
e.preventDefault();
this.context.router.transitionTo(e.target.href)
}
render() {
return (
<Link to={"/next-page"}
onClick={this.navigateProgramatically}
>Continue</Link>
);
}
}
NavigateNext.contextTypes = {
router: React.PropTypes.object
};
transitionTo is just one of available router methods. router object also contains blockTransitions(getPromptMessage), createHref(to) and replaceWith(loc) which are worth checking out.
Here's official react-router tutorial that mentions above method.
If you wanna learn more about using react's context check out the docs.
I don't have enough reputation to comment, but in answer to #singularity's question, you have to include the context properties you wish to make available on the component class' contextTypes static property.
From the React docs on context:
If contextTypes is not defined, then context will be an empty object.
In this case:
class NavigateNext extends React.Component {
// ...
static contextTypes = {
router: PropTypes.object
}
// ...
}
Unlike propTypes, contextTypes actually cause React to behave differently and is not only for typechecking.
Using withRouter will add router properties to you component, then you can access the history and use push like you did with v3:
import React from 'react';
import { withRouter } from 'react-router-dom';
class Form extends React.Component {
constructor(props) {
super(props);
this.state = {
input: '',
};
this._submit = this._submit.bind(this);
}
render() {
return (
<form onSubmit={this._submit}>
<input type="text" onChange={(event) => this.setState({input: event.target.value})}/>
<button type="submit">Submit</button>
</form>
);
}
_submit(event) {
event.preventDefault();
this.props.history.push(`/theUrlYouWantToGoTo`);
}
}
export default withRouter(Form);
react-router v4 beta is released and the API changed a little bit. Instead of this.context.router.transitionTo(e.target.href) Do, this.context.router.push(e.target.href) if you are using latest version.
Link to new doc: https://reacttraining.com/react-router/#context.router
If you need to navigate outside of a component at a location that you are unable to pass in the history object from a component similar to how you would do with browserHistory in older versions you can do the following.
First create a history module
History.js:
import createBrowserHistory from 'history/createBrowserHistory'
export default createBrowserHistory();
Then when you are declaring the Router make sure to import Router from react-router and not react-router-dom (which is just a wrapper to react-router version but creates history object automatically) and pass in the history module you just created
Root.js (or wherever you do this):
import Router from 'react-router/Router'
import history from './history'
...
class Root extends Component{
render() {
return (
<Router history={history}>
...
</Router>
);
}
}
Now your application will use the custom created history you created. You can now import that history module anywhere and just do history.replace and so forth just like you would of done with browserHistory in the past.
SomeModule.js:
import history from './history';
export default ()=>{
// redirecting to login page using history without having to pass it in
// from a component
history.replace('/login')
}
Of course this is not the recommended way just as using browserHistory in the old versions was not the recommended way since things like server side rendering won't work, but if you don't care about that this can often be the right solution.
An extra benefit doing this is you could augment the history object to things lie parsed query string params like this for example:
import createBrowserHistory from 'history/createBrowserHistory'
import queryString from 'query-string';
const history = createBrowserHistory();
history.location.query = queryString.parse(history.location.search);
history.listen(() => {
history.location.query = queryString.parse(history.location.search);
});
export default history;
If you need to access history outside of components (for example in redux actions) react-router has published their original solution here.
Basically you have to create your own history object:
import { createBrowserHistory } from 'history';
const history = createBrowserHistory();
And pass it to your router:
import { Router } from 'react-router-dom';
ReactDOM.render((
<Router history={history}> // <<-- the history object
<App/>
</Router>
), document.getElementById('root'))
Note: you have to use plain Router instead of BrowserRouter or HashRouter here!
If you export the history now, you can work with it anywhere:
import history from './history';
history.push('/home');
I found using state, a ternary operator and <Redirect> worked best. I think this is also the prefered way since it is closest to the way v4 is set up.
In the constructor()
this.state = {
redirectTo: null
}
this.clickhandler = this.clickhandler.bind(this);
In the render()
render(){
return (
<div>
{ this.state.redirectTo ?
<Redirect to={{ pathname: this.state.redirectTo }} /> :
(
<div>
..
<button onClick={ this.clickhandler } />
..
</div>
)
}
In the clickhandler()
this.setState({ redirectTo: '/path/some/where' });
Hope it helps. Let me know.
use withRouter:
import React, { PropTypes } from 'react'
import { withRouter } from 'react-router'
// A simple component that shows the pathname of the current location
class ShowTheLocation extends React.Component {
static propTypes = {
match: PropTypes.object.isRequired,
location: PropTypes.object.isRequired,
history: PropTypes.object.isRequired
}
render() {
const { match, location, history } = this.props
return (
<div>You are now at {location.pathname}</div>
)
}
}
// Create a new component that is "connected" (to borrow redux
// terminology) to the router.
const ShowTheLocationWithRouter = withRouter(ShowTheLocation)
It is really difficult with react-router. None of the options are straight-forward. this.props.history gave me undefined. But
window.location='/mypath/';
worked for me in version 5.0.0. Don't know whether it is the right method.

Resources