Update Parent Component State from Child Component - javascript

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>

Related

Pass props or data beteween components React

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.

React children props are always equal

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.

How would I restructure my React App, so that I can pass state down to a component on a different route

I have three components routed to different paths. I want to restructure my App so that I can pass state via props from my SubmitProject Component to my Portfolio Component I still want them to have separate paths ie; /portfolio and /SubmitProject I plan to have two browserwindows open to test that when I submit a form on SubmitProject it will show up on Portfolio then I will be using firebase to persist my state to a database.
Do I need to have my state be at a top level Component like App.js and then have the BrowserRouter inside of that? If so how do I recreate the connections I have made from <SubmitProject/> -> <PortfolioForm/> -> <FormAdd/>
My Desired Goal is that when I submit the form from the FormAdd Component when I am on the /submit Route that it will output via state on my Portfolio Component on the /Portfolio Route.
It has been recommend to use a state manager like context api, or something else, but I want to know if there is a way to restructure my App and be able to pass state from a top level component that each component and route share.
Here is my relevant code
components/Router.js
import React from 'react';
import {BrowserRouter, Route, Switch} from 'react-router-dom';
import Portfolio from './Portfolio';
import SubmitProject from './SubmitProject';
import App from './App';
const Router = () => (
<BrowserRouter>
<Switch>
<Route exact path="/" component={App}/>
<Route exact path="/portfolio" component={Portfolio}/>
<Route exact path="/submit" component={SubmitProject}/>
</Switch>
</BrowserRouter>
);
export default Router;
components/App.js // Should My Router be in here?
import React from 'react';
class App extends React.Component {
render() {
return <div>Test</div>
}
}
export default App;
/components/SubmitProject.js
import React from 'react';
import PortfolioForm from './PortfolioForm';
import Section from './Section';
class SubmitProject extends React.Component {
state = {
sections:{}
};
addSection = section =>{
const sections = {...this.state.sections};
sections[`section${Date.now()}`] = section;
this.setState({
sections: sections
});
}
render() {
return(
<React.Fragment>
<h1>Submit Project</h1>
<h2>Enter Project Data</h2>
<ul className="section">
{Object.keys(this.state.sections).map(key => <Section key={key} details={this.state.sections[key]}/>)}
</ul>
<PortfolioForm addSection={this.addSection} />
</React.Fragment>
)
}
}
export default SubmitProject;
/components/PortfolioForm.js
import React from 'react';
import FormAdd from './FormAdd';
class Portfolio extends React.Component {
render() {
return(
<React.Fragment>
<h1>Submit Form</h1>
<FormAdd addSection={this.props.addSection}/>
</React.Fragment>
)
}
}
export default Portfolio;
/components/FormAdd.js
import React from 'react';
class FormAdd extends React.Component {
nameRef = React.createRef();
createSection = (event) =>{
event.preventDefault();
const section = {
name: this.nameRef.current.value
};
this.props.addSection(section);
};
render() {
return(
<React.Fragment>
<form onSubmit={this.createSection}>
<input type="text" ref={this.nameRef} name="name" placeholder="Name"/>
<button type="submit">+ Add Section</button>
</form>
</React.Fragment>
)
}
}
export default FormAdd;
/components/Portfolio.js
import React from 'react';
class Portfolio extends React.Component {
//CAN I GET STATE FROM SubmitProject.js FILE IN HERE? By Restructuring my App Somehow.
render() {
return(
<React.Fragment>
<h1>Portfolio Page</h1>
<h2>List of projects</h2>
</React.Fragment>
)
}
}
export default Portfolio;
UPDATED CODE
I am now getting an error that says FooContext is not defined
components/App.js
import React from 'react';
import SubmitProject from './SubmitProject';
import {BrowserRouter, Route, Switch} from 'react-router-dom';
const FooContext = React.createContext();
class App extends React.Component {
state = {
sections:{}
};
addSection = section =>{
const sections = {...this.state.sections};
sections[`section${Date.now()}`] = section;
this.setState({
sections: sections
});
}
render() {
return (
<FooContext.Provider value={this.state.sections}>
<Router/>;
</FooContext.Provider>
)
}
}
class Router extends React.PureComponent {
render() {
return
<BrowserRouter>
<Switch>
<Route exact path="/" component={Root} />
</Switch>
</BrowserRouter>
}
}
const Root = props => <FooContext.Consumer>{sections => <SubmitProject/> }</FooContext.Consumer>;
export default App;
UPDATED CODE V#2
App.js
import React from 'react';
import SubmitProject from './SubmitProject';
import Home from './Home';
import {BrowserRouter, Route, Switch} from 'react-router-dom';
const FooContext = React.createContext();
class App extends React.Component {
state = {
sections:{}
};
addSection = section =>{
const sections = {...this.state.sections};
sections[`section${Date.now()}`] = section;
this.setState({
sections: sections
});
}
render() {
return (
<FooContext.Provider value={this.state.sections}>
<Router/>;
</FooContext.Provider>
)
}
}
class Router extends React.PureComponent {
render() {
return (
<BrowserRouter>
<Switch>
<Route exact path="/" component={Home} />
<Route exact path="/portfolio" component={Portfolio} />
</Switch>
</BrowserRouter>
)
}
}
const Portfolio = props => <FooContext.Consumer>{foo => <SubmitProject/>}</FooContext.Consumer>;
export default App;
SubmitProject.js
import React from 'react';
import PortfolioForm from './PortfolioForm';
import Section from './Section';
class SubmitProject extends React.Component {
render() {
return(
<React.Fragment>
<h1>Submit Project</h1>
<h2>Enter Project Data</h2>
<ul className="section">
{Object.keys(this.state.sections).map(key => <Section key={key} details={this.state.sections[key]}/>)}
</ul>
<PortfolioForm addSection={this.addSection} />
</React.Fragment>
)
}
}
export default SubmitProject;
It has been recommend to use a state manager like context api, or something else, but I want to know if there is a way to restructure my App and be able to pass state from a top level component that each component and route share.
There are problems with this approach.
Considering that App maintains application state, it's necessary to pass it to <Router> as a prop and then to route components that depend on it:
class App extends React.Component {
state = { foo: true };
render() {
return <Router foo={this.state.foo}/>
}
}
const Router = props => (
const RootWithFoo = props => <Root foo={props.foo}/>;
return <BrowserRouter>
<Switch>
<Route exact path="/" component={RootWithFoo} />
...
</Switch>
</BrowserRouter>
);
This puts a restriction on component structure; in order to avoid deeply passed props, Router component should be removed, and Route should be rendered directly in App:
class App extends React.Component {
state = { foo: true };
render() {
const RootWithFoo = props => <Root foo={this.state.foo}/>;
return <BrowserRouter>
<Switch>
<Route exact path="/" component={RootWithFoo} />
...
</Switch>
</BrowserRouter>
}
}
This is a problem that context API and state management libraries (e.g. Redux) address. They allow to provide global state for nested components where it's used.
Another problem is performance. The whole router will be re-rendered on each state update. Again, context API and state management libraries address this. As context API manual states:
All Consumers that are descendants of a Provider will re-render whenever the Provider’s value prop changes. The propagation from Provider to its descendant Consumers is not subject to the shouldComponentUpdate method, so the Consumer is updated even when an ancestor component bails out of the update.
So if context provider value updates, it's unnecessary to re-render the whole tree. Context consumer will be re-rendered regardless of this. Since the whole tree will be re-rendered by default, Provider child(ren) should be a pure component to avoid unnecessary re-renders. This is a reason why separated App and Router components may be preferable:
class App extends React.Component {
state = { foo: true };
render() {
return <FooContext.Provider value={this.state.foo}>
<Router/>;
</FooContext.Provider>
}
}
class Router extends React.PureComponent {
render() {
return <BrowserRouter>
<Switch>
<Route exact path="/" component={Root} />
...
</Switch>
</BrowserRouter>
}
}
const Root = props => <FooContext.Consumer>{foo => ...}</FooContext.Consumer>;
When global state is updated, only App and route components that depend on it (Root, etc.) are re-rendered but not Router.

ReactJS propagate data between Components

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

How does react-router pass props to other components with <Links> and <Route>?

I have a select input to make a newCountry's Value in PageA1.And I want to pass this value to PageB .But the console will warning: [react-router] You cannot change <Router routes>; it will be ignored when I select newCountry .
How can I use <Links> and <Route> to pass the newCountry's Value?
PageA.js:
export default class PageA extends Component{
constructor(props){
super(props);
this.state = {
country:''
}
}
// 收取country的callback
selectCountry = (newCountry)=>{
this.setState({country:newCountry});
this.props.callbackPageA(newCountry);
}
render(){
return(
<div>
<div>
<PageA1 callbackCountry={this.selectCountry} />
</div>
</div>
);
}
}
My router App.js:
export default class App extends React.Component {
constructor(props){
super(props);
this.state={
country:''
}
}
PageAValue = (newCountry) =>{
this.setState({
country:newCountry
})
}
render () {
const CountryProps = this.state.country;
console.log('app '+CountryProps);
return(
<Router history={browserHistory}>
<Route path="/" component={HomePage}></Route>
<Route path="/PageA" component={() => <PageA callbackPageA={this.PageAValue}/>}></Route>
<Route path="/PageB" component={PageB} CountryProps={CountryProps}>
</Route>
</Router>
)
}
}
PageB.js:
export default class PageB extends React.Component {
constructor(props){
super(props);
}
render () {
const country = this.props.route.CountryProps;
console.log('corepage ' + country);
return(
<div>
<div>
<div><PageD country={country}/></div>
</div>
</div>
)
}
}
You can attach state to the location descriptor. For example you can define the to prop for a <Link> as:
<Link to={{ pathname: '/PageB', state: { country: 'Ireland' }}} />
Then in your PageB component, the location will be available as a prop.
The <Route> components are purely for configuration and aren't actually rendered, which is why you are getting that warning.

Categories