I was trying to implement an ErrorBoundary HoC component for error handling as per React16 Doc. I made the ErrorBoundary component as PureComponent and I noticed that the children props are always equal and it tried to prevent the re-rendering of the child components.
class ErrorBoundary extends React.PureComponent {
constructor(props) {
super(props);
this.state = { hasError: false };
}
componentDidCatch(error, info) {
this.setState({ hasError: true });
}
render() {
if (this.state.hasError) {
return <PageNotFound />;
}
return this.props.children;
} }
After modifying the component to React.Component and added a componentDidUpdate, I was able to see that the children props are always equal.
componentWillUpdate(nextProps, nextState, nextContext) {
if(this.props.children === nextProps.children){ //returns TRUE
console.log('children value are equal')
}
}
The below code shows how I used the ErrorBoundary Component
<BrowserRouter>
<ErrorBoundary>
<Route path='/' component={Router} />
</ErrorBoundary>
</BrowserRouter>
Can anyone explain how the children prop is equal?
children always refers to values that were passed as component children, i.e. <Route> React element:
<ErrorBoundary>
<Route path='/' component={Router} />
</ErrorBoundary>
Since parent component didn't rerender, <Route> object is the same.
Related
Good afternoon friends,
My pages and components are arranged in the main class of my application, can I pass some results from any component or page to the main class and get this property from main class to any other component.
To describe question well I will show an example:
This is my main class App.js
import React, {Component} from 'react';
import { BrowserRouter as Router, Route} from "react-router-dom";
import HomePage from "./Pages/HomePage";
import NavBar from "./Components/NavBar";
import PaymentStatus from "./Pages/PaymentStatus";
class App extends Component {
constructor(props) {
super(props);
this._isMounted = true;
this.state = {};
};
render() {
return (
<Router>
<NavBar/>
<Route name={'Home'} exact path={'/'} component={HomePage}/>
<Route name={'PaymentStatus'} exact path={'/payment-status/:tId'} component={PaymentStatus}/>
</Router>
);
}
}
export default App;
Now my navigation bar component: NavBar.js
import React, {Component} from 'react';
class NavBar extends Component {
constructor(props) {
super(props);
this._isMounted = true;
this.state = {};
};
_makeSomething =async() => {
// Somw function that returns something
}
render() {
return (
<div>
<div id={"myNavbar"}>
<div>
<a onClick={()=>{this._makeSomething()}} href={'/'}/> Home</a>
<a onClick={()=>{this._makeSomething()}} href={"/payment-status"} />Payment Status</a>
</div>
</div>
);
}
}
export default NavBar;
HomePage.js
import React, {Component} from 'react';
class HomePage extends Component {
constructor(props) {
super(props);
this._isMounted = true;
this.state = {};
};
async componentDidMount() {
console.log(this.props.match.params.tId)
};
render() {
return (
<div>
<div id={"main"}>
<div>
<p>This is home page</p>
</div>
</div>
);
}
}
export default HomePage;
PaymentStatusPage.js
import React, {Component} from 'react';
class PaymentStatusPage extends Component {
constructor(props) {
super(props);
this._isMounted = true;
this.state = {};
};
async componentDidMount() {
console.log(this.props.match.params.tId)
};
render() {
return (
<div>
<div id={"status"}>
<div>
<p>This is payment Status Page</p>
</div>
</div>
);
}
}
export default PaymentStatusPage;
Now here is the question:
Can I pass to App.js events (or props) when HomePage.js or PaymentStatusPage.js or when something was changed in NavBar.js
Also, want pass received peprops to any component.
Thank you.
You can decalare method in class App and then pass it to another component via props.
For example
Then you can call this method in MyComponent and pass some value to it. This is the way you pass value from subcomponent to parent component. In method in App you can simply use setState.
What's left to do is to pass this new state attribute to another component via props.
To pass value to component, while using you have to change
<Route component={SomeComponent}
To
<Route render={() => <SomeComponent somethingChanged={this.somethingChangedMethodInAppClass}}/>
Hope it helps!
EDIT: You can also use Redux to externalize state and reuse it in child components
You have two options here:
Keep all of your state in your parent component, App, and pass any props down to your children component, even actions that could update the parent state. If another children uses that state, then that child will be rerendered too.
Manage your state with Redux and make it available for all your components.
I created a small example out of your scenario.
In this example, the App component has a state with a property called title and a function that is passed down via props to the Navbar.
class App extends Component {
constructor(props) {
super(props);
this._isMounted = true;
this.state = {
title: "Home Page"
};
}
_makeSomething = title => {
this.setState({ title: title });
};
render() {
return (
<Router>
<NavBar clicked={this._makeSomething} />
<Route
name={"Home"}
exact
path={"/"}
component={() => <HomePage title={this.state.title} />}
/>
<Route
name={"PaymentStatus"}
exact
path={"/payment-status/:tId"}
component={() => <PaymentStatus title={this.state.title} />}
/>
</Router>
);
}
}
The components HomePage and PaymentStatus will get that title as props from the App's state and NavBar will get the _makeSomething function as props. So far, all that function does is update the state's title.
class NavBar extends Component {
constructor(props) {
super(props);
this._isMounted = true;
this.state = {};
}
render() {
return (
<div>
<div id={"myNavbar"}>
<NavLink
onClick={() => {
this.props.clicked("Home Page");
}}
to={"/"}
>
{" "}
Home
</NavLink>
<NavLink
onClick={() => {
this.props.clicked("Payment Page");
}}
to={"/payment-status/1"}
>
Payment Status
</NavLink>
</div>
</div>
);
}
}
In the Navbar, when the function I passed down from App as props is clicked, it will go all the way back to the App component again and run _makeSomething, which will change the App's title.
In the mantime, the components HomePage and PaymentStatus received title as props, so when the state's title is changed, these two children component will change too, since their render function relies on this.props.title.
For example, HomePage:
class HomePage extends Component {
constructor(props) {
super(props);
this._isMounted = true;
this.state = {};
}
async componentDidMount() {
console.log(this.props.match.params.tId);
}
render() {
return (
<div>
<div id={"main"}>
<p>This is {this.props.title}</p>
</div>
</div>
);
}
}
Like I said before, by keeping your state in the parent component and sending down to the children component just what they need, you should be able to accomplish what you need.
A note: I did change the anchor tag from <a> to NavLink which is what you're supposed to use with react-router-dom if you don't want a complete refresh of the page.
The full code can be found here:
Have a look at Context. With this you can pass an object from a Provider to a Consumerand even override properties with nested providers: https://reactjs.org/docs/context.html
AppContext.js
export const AppContext = React.createContext({})
App.js
someFunction = ()=>{
//implement it
}
render() {
const appContext = {
someFunction: this.someFunction
}
return (
<AppContext.Provider value={appContext}>
<Router>
<NavBar/>
<Route name={'Home'} exact path={'/'} component={HomePage}/>
<Route name={'PaymentStatus'} exact path={'/payment-status/:tId'} component={PaymentStatus}/>
</Router>
</AppContext>
);
}
Homepage.js
class HomePage extends Component {
constructor(props) {
super(props);
this._isMounted = true;
this.state = {};
};
async componentDidMount() {
console.log(this.props.match.params.tId)
this.props.appContext.someFunction(); //calls the function of the App-component
};
render() {
return (
<div>
<div id={"main"}>
<div>
<p>This is home page</p>
</div>
</div>
);
}
}
export default (props) => (
<AppContext.Consumer>
{(appContext)=>(
<HomePage {...props} appContext={appContext}/>
)}
</AppContext.Consumer>
)
You can also use this mechanic with function components. I'm normally encapsulating the Consumer to an extra component. So all values available for the component as normal property and not just inside the rendered components.
I have a situation that want to call a Child.method from Parent, though this is not a best practice, but I just want to give it a try. And I decide to use ref.current.setState() in the Parent component.
Here is the example code.
https://codesandbox.io/s/8lmvq3yq68
There are some unexpected behavior happened where ref, setState and react-router-dom are used together. When I use Redirect and ref together, the Child.componentDidUpdate will not be called. I wonder if it is a valid code in React? Since I can not find any doc that showing it is invalid. Or is it a bug of react-router-dom?
I logged in some additional component lifecycle logs to clarify.
So what's happening is when you select the 'Link' to '/' There are 2 parallel calls that will fire.
Navigate to the '/' route
The onClick event
So one will first navigate to the relevant route while the other will trigger the ref's function. You will see the "Calling the state change" log.
What happens when you route to the '/' path is that the SuperHero
component is unmounted! And you will see the "Unmounting - Superhero"
log. So the state change is lost and componentDidUpdate will not be fired. Nevertheless, as you can see, the redirect does happen
and the component is mounted again. Now it has no sense of the state
change but the "Mounting - Superhero" will be logged.
I have further included a separate button. Since there is no unmounting, this works as you expect!
https://codesandbox.io/s/81v0mz0548
import React from "react";
import ReactDOM from "react-dom";
import {
BrowserRouter as Router,
Link,
Redirect,
Route
} from "react-router-dom";
import "./styles.css";
class App extends React.Component {
constructor(props) {
super(props);
this.superheroElement = React.createRef();
}
handleClick = () => {
this.superheroElement.current.changeName();
};
render() {
return (
<Router>
<Link to={"/"} onClick={this.handleClick}>
haha
</Link>
<Route path="/" exact={true} render={() => <Redirect to="/post" />} />
<Route
path="/post"
render={props => <Superhero ref={this.superheroElement} {...props} />}
/>
<br/>
<button onClick={this.handleClick}>
haha
</button>
</Router>
);
}
}
class Superhero extends React.Component {
constructor(props) {
super(props);
this.state = {
name: "Batman"
};
this.changeName = this.changeName.bind(this);
}
changeName() {
console.log("Calling the state change");
this.setState({
name: "Bruce Wayne"
});
}
componentDidUpdate() {
// Not called because component is unmounted
console.log("Updating - Superhero");
}
componentDidMount() {
console.log("Mounting - Superhero");
}
componentWillUnmount() {
console.log("Unmounting - Superhero");
}
render() {
return <div>{this.state.name}</div>;
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
I am setting a state into child component on event perform and want to sent this to Parent component. I searched for this on SO. But still didn't found any way to do this.
Let say i have a parent component Home, and have child component User. I am performing some event in User component, and at that time, i want to pass data to Home component. How can i do this?
Below is my code:
/* Parent component */
import React, { Component } from 'react';
import User from './user';
class Home extends React.Component{
constructor(props){
super(props)
this.state = {
isReportSent: false
}
}
render(){
<Switch>
<Route exact path="/" component={User}/>
</Switch>
}
}
/* child component */
class User extends React.Component{
constructor(props){
super(props)
}
render(){
}
}
Note: My parent component is Routing component, in which i am routing my child component on particular path. So can't pass any function to child component.
You can make use of render props in Routes to pass callback method to child which you can then use to update the parent
import React, { Component } from 'react';
import User from './user';
class Home extends React.Component{
constructor(props){
super(props)
this.state = {
isReportSent: false
}
}
performUpdate = () => {
}
render(){
<Switch>
<Route exact path="/" render={(props) => <User {...props} performUpdate={this.performUpdate}/>}/>
</Switch>
}
}
/* child component */
class User extends React.Component{
constructor(props){
super(props)
}
handleClick=() => {
this.props.performUpdate(); //call to inform parent about change
}
render(){
}
}
You can actually pass a method, you just need to use the render prop
<Switch>
<Route exact path="/" render={(props)=><User {...props} someMethod={someMethod} />}/>
</Switch>
I'm using React Router to create a multi page app. My main component is <App/> and it renders all of the routing to to child components. I'm trying to pass props via the route, and based on some research I did, the most common way for child components to tap into props passed down is via the this.props.route object that they inherit. However, this object is undefined for me. On my render() function in the child component, I console.log(this.props) and am return an object that looks like this
{match: Object, location: Object, history: Object, staticContext: undefined}
Doesn't look like the props I expected at all. Here is my code in detail.
Parent Component (I'm trying to pass the word "hi" down as a prop called "test" in all of my child components):
import { BrowserRouter as Router, HashRouter, Route, Switch } from 'react-router-dom';
import Link from 'react-router';
import React from 'react';
import Home from './Home.jsx';
import Nav from './Nav.jsx';
import Progress from './Progress.jsx';
import Test from './Test.jsx';
export default class App extends React.Component {
constructor() {
super();
this._fetchPuzzle = this._fetchPuzzle.bind(this);
}
render() {
return (
<Router>
<div>
<Nav />
<Switch>
<Route path="/" exact test="hi" component={Home} />
<Route path="/progress" test="hi" component={Progress} />
<Route path="/test" test="hi" component={Test} />
<Route render={() => <p>Page not found!</p>} />
</Switch>
</div>
</Router>
);
}
}
Child:
import React from 'react';
const CodeMirror = require('react-codemirror');
import { Link } from 'react-router-dom';
require('codemirror/mode/javascript/javascript')
require('codemirror/mode/xml/xml');
require('codemirror/mode/markdown/markdown');
export default class Home extends React.Component {
constructor(props) {
super(props);
console.log(props)
}
render() {
const options = {
lineNumbers: true,
theme: 'abcdef'
// mode: this.state.mode
};
console.log(this.props)
return (
<div>
<h1>First page bro</h1>
<CodeMirror value='code lol' onChange={()=>'do something'} options={options} />
</div>);
}
}
I'm pretty new to React so my apologies if I'm missing something obvious.
Thanks!
You can pass props to the component by making use of the render prop to the Route and thus inlining your component definition. According to the DOCS:
This allows for convenient inline rendering and wrapping without the
undesired remounting explained above.Instead of having a new React
element created for you using the component prop, you can pass in a
function to be called when the location matches. The render prop
receives all the same route props as the component render prop
So you can pass the prop to component like
<Route path="/" exact render={(props) => (<Home test="hi" {...props}/>)} />
and then you can access it like
this.props.test
in your Home component
P.S. Also make sure that you are passing {...props} so that the
default router props like location, history, match etc are also getting passed on to the Home component
otherwise the only prop that is getting passed down to it is test.
I am working on an SPA with ReactJS. I have a root component App and then several child components. In the the App component I am trying to store some application level state such as logged in user id, and other data. However I am not seeing my state be propagated down the child components.
App
import { Router, Route, Link, IndexRoute, browserHistory, hashHistory } from 'react-router';
import ParameterContainer from './components/parameter/parameter-container';
import NavMenu from './components/navigation/nav-menu';
import {Alert} from 'react-bootstrap';
import SelectFilter from './components/sample/sample-container';
// Main component and root component
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
userId: null,
roles: null,
parameterTypes: {
'STRING': 'STRING',
'BOOLEAN': 'BOOLEAN',
'INTEGER': 'INTEGER',
'DECIMAL': 'DECIMAL'
}
};
}
render() {
return (
<div>
<NavMenu />
<div className="container">
{this.props.children}
</div>
</div>
)
}
}
// page for 404
class NoMatch extends React.Component {
render() {
return (
<div className="container">
<Alert bsStyle="danger">
<h1>404: Not Found</h1>
<h3>The requested resource does not exist!</h3>
</Alert>
<img src="images/404.png" style={{display: 'block', margin: '0 auto', width: 300, height: '*'}} />
</div>
)
}
}
// render the application
ReactDOM.render((
<Router history={hashHistory}>
<Route path="/" component={App}>
<Route path="parameter" component={ParameterContainer} />
<Route path="sample" component={SelectFilter} />
<Route path="*" component={NoMatch}/>
</Route>
</Router>
), document.getElementById('react'))
Child Component
import React from 'react';
export default class ParameterContainer extends React.Component {
constructor(props) {
super(props);
this.state = { parameters: [] };
this.client = rest.wrap(mime);
this.fetchFromApi = this.fetchFromApi.bind(this);
console.log('Props:' + props);
}
render() {
....
}
The this.props does not contain what I expected. I need to pass data down to children components.
State is not propagated to child components - you have to set props on child components to pass down data. You can use React.cloneElement to add properties to children:
let childrenWithProps = React.cloneElement({this.props.children, {
userid: this.state.userId,
...
});
The best way would be to use Redux to manage application data and store application level state in Redux store. If you don't use Redux you can also consider using react Context. According to docs you can use Context if:
you want to pass data through the component tree without having to
pass the props down manually at every level
To pass the state or props to the child component you can explicit them on your Route node
<Route path="parameter" component={ParameterContainer} parentState={this.state} />
You can, then, access them in the child component as props
constructor(props) {
super(props)
console.log('parentState:' + props.parentState);
}
Here are better and more detailed answers: react-router - pass props to handler component