Update the state of the grandparent component - javascript

I'd like to update a state value of the grandparent component:
class GrandChild extends React.Component {
changeNumber() {
// this.setState(prevState => ({
// number: prevState.number + 1 // Add 1
// }));
// I want to set the state of the App Component instead of the GrandChild component
}
render() {
return(
<div>
<h1>The number is {this.props.number}</h1>
<button onClick={this.changeNumber}>Increase number by 1</button>
</div>
)
}
}
class Child extends React.Component {
render() {
return(
<div>
<GrandChild number={this.props.number}/>
</div>
)
}
}
class App extends React.Component {
constructor() {
super()
this.state = {
number: 1
}
}
render() {
return (
<Child number={this.state.number}/>
);
}
}
ReactDOM.render(<App />, document.getElementById('root'));
I did code this in CodePen for those who want to test the code: https://codepen.io/anon/pen/oEeQdr
I hope there's a simple solution for this.
Thanks in advance.

class GrandChild extends React.Component {
changeNumber=()=> {
this.props.changeNumber();//call parent `changeNumber` method
}
render() {
return(
<div>
<h1>The number is {this.props.number}</h1>
<button onClick={this.changeNumber}>Increase number by 1</button>
</div>
)
}
}
class Child extends React.Component {
render() {
return(
<div>
<GrandChild number={this.props.number} changeNumber={this.props.changeNumber} /> //passed `changeNumber` as it is to `GrandChild`
</div>
)
}
}
class App extends React.Component {
constructor() {
super()
this.state = {
number: 1
}
}
changeNumber=()=>{
this.setState((prevState)=>{
console.log(prevState);
return {
number : prevState.number + 1
}
});
}
render() {
return (
<Child number={this.state.number} changeNumber = {this.changeNumber}/> //passed `changeNumber` to Child
);
}
}
ReactDOM.render(<App />, document.getElementById('root'));
Working codepen

Check working code below:
import React from 'react';
import ReactDOM from 'react-dom';
class GrandChild extends React.Component {
render() {
return (
<div>
<h1>The number is {this.props.number}</h1>
<button onClick={this.props.incNumber}>Increase number by 1</button>
</div>
);
}
}
class Child extends React.Component {
render() {
return (
<div>
<GrandChild number={this.props.number} incNumber={this.props.incNumber} />
</div>
);
}
}
class App extends React.Component {
constructor() {
super();
this.state = {
number: 1
};
}
incrementNumber = () => {
this.setState({ number: this.state.number + 1 });
};
render() {
return (
<Child number={this.state.number} incNumber={this.incrementNumber} />
);
}
}
ReactDOM.render(<App />, document.getElementById('root'));

You can send method body as a prop to Child Class, as a pointer to it, and when you wil lcal this method in the Child, it will properly executed in the Parent, where was declared.
class GrandChild extends React.Component {
render() {
return (
<div>
<h1>The number is {this.props.passedProps.number}</h1>
<button onClick={() =>
this.props.passedProps.increaseNumber()}>Increase number by 1</button>
</div>
);
}
}
class Child extends React.Component {
render() {
return (
<div>
<GrandChild passedProps={this.props}/>
</div>
);
}
}
class App extends React.Component {
constructor() {
super()
this.state = {
number: 1
}
}
increaseNumber = () => {
this.setState({ number: this.state.number + 1 });
}
render() {
return (
<Child number={this.state.number} increaseNumber={this.increaseNumber}/>
);
}
}
ReactDOM.render(<App />, document.getElementById('root'));

Related

The proper way of exchanging objects between sibling components in React

I am trying to build a simple React app and stuck into trouble. I have the following structure:
App.js
class App extends React.Component {
render() {
return (
<div className="App">
<PlanBuilder />
<PlanMenu />
</div>
);
}
}
PlanMenu.js
class PlanMenu extends React.Component {
render() {
return (
<div className="PlanMenu">
<button type="button"
onClick={addObject(
new CWall({
x: 100,
y: 100,
length: 200
}))}>Wall
</button>
</div>
);
}
}
PlanBuilder.js
class PlanBuilder extends React.Component {
constructor(props) {
super(props);
this.state = {
objects: []
};
}
addObject(object) {
this.setState({
objects: [...this.state.objects, object]
});
}
render() {
return (
<Stage>
<Layer>
{
this.state.objects.map(function(object) {
return object.render();
})
}
</Layer>
</Stage>
);
}
So the main idea is that I have two sibling components: the drawing area and the menu. When the button on the menu is pressed, I want to create a new object and send it to the drawing area element. So, the question is how to pass PlanBuilder.addObject method to the PlanMenu class. I came from the C world, and what I think about is to pass kinda function pointer to PlanMenu. However, I am not sure this is an appropriate solution. Would you please recommend me the proper way of doing this in React? Thank you in advance.
In this case you have two ways.
The simpler one is to move the logic you have on PlanBuilder to App, and pass the necessary props to PlanBuilder and PlanMenu, like:
class PlanMenu extends React.Component {
render() {
const { addObject } = this.props
return (
<div className="PlanMenu">
<button type="button"
onClick={addObject(
new CWall({
x: 100,
y: 100,
length: 200
}))}>Wall
</button>
</div>
);
}
}
class PlanBuilder extends React.Component {
render() {
const { objects } = this.props
return (
<Stage>
<Layer>
{objects.map(function(object) {
return object.render();
})}
</Layer>
</Stage>
)
}
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
objects: []
};
this.addObject = this.addObject.bind(this)
}
addObject(object) {
this.setState({
objects: [...this.state.objects, object]
});
}
render() {
const { objects } = this.state
return (
<div className="App">
<PlanBuilder objects={objects} />
<PlanMenu addObject={this.addObject} />
</div>
);
}
}
The other alternative is to create a "Container" to hold the logic instead adding it to App, like:
class PlanMenu extends React.Component {
render() {
const { addObject } = this.props
return (
<div className="PlanMenu">
<button type="button"
onClick={addObject(
new CWall({
x: 100,
y: 100,
length: 200
}))}>Wall
</button>
</div>
);
}
}
class PlanBuilder extends React.Component {
render() {
const { objects } = this.props
return (
<Stage>
<Layer>
{objects.map(function(object) {
return object.render();
})}
</Layer>
</Stage>
)
}
}
class PlanContainer extends React.Component {
constructor(props) {
super(props);
this.state = {
objects: []
};
this.addObject = this.addObject.bind(this)
}
addObject(object) {
this.setState({
objects: [...this.state.objects, object]
});
}
render() {
const { objects } = this.state
return (
<div>
<PlanBuilder objects={objects} />
<PlanMenu addObject={this.addObject} />
</div>
)
}
}
class App extends React.Component {
render() {
return (
<div className="App">
<PlanContainer />
</div>
);
}
}
In my opinion, creating a Container makes your code more readable, reusable and cleaner :)
Hope it help!

What is the difference between this.state.function and this.function in ReactJS

I am learning the concept of States in React. I am trying to understand the difference between using this.handleChange, and this.state.handleChange.
I would be grateful if someone could explain to me, the exact difference between the two, and why would this.state.handleChange not work?
class MyApp extends React.Component {
constructor(props) {
super(props);
this.state = {
inputValue: ''
}
this.handleChange = this.handleChange.bind(this);
}
handleChange(event) {
this.setState({
inputValue: event.target.value
});
}
render() {
return (
<div>
< GetInput input={this.state.inputValue} handleChange={this.handleChange} />
{ /* this.handleChanges, and this.state.handleChanges */ }
< RenderInput input={this.state.inputValue} />
</div>
);
}
};
class GetInput extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<h3>Get Input:</h3>
<input
value={this.props.input}
onChange={this.props.handleChange}/>
</div>
);
}
};
class RenderInput extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<h3>Input Render:</h3>
<p>{this.props.input}</p>
</div>
);
}
};
You can technically call this.state.handleChange so long as you add handleChange in your state.
But it doesn't really make sense since you don't want React to keep a track of it, and it will probably not change (unless you are doing some clever tricks).
constructor(props) {
super(props);
this.state = {
handleChange: e => {
e.preventDefault();
console.log("this.state.handleChange");
}
};
}
One would normally declare a member function in a class.
handleChange = e => {
e.preventDefault();
console.log("this.handleChange");
};
Here is the full working code
(working demo available on CodeSandBox).
import React from "react";
import ReactDOM from "react-dom";
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
handleChange: e => {
e.preventDefault();
console.log("this.state.handleChange");
}
};
}
handleChange = e => {
e.preventDefault();
console.log("this.handleChange");
};
render() {
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<button onClick={this.handleChange}>this.handleChange</button>
<button onClick={this.state.handleChange}>
this.state.handleChange
</button>
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
When you say this.state.something this means something is in the state field of the class. When you say this.someFunction this means something is in the class itself. this here is pointing out our class.
class App extends React.Component {
state = {
something: "Something",
}
someFunction = () => console.log(this.state.something);
render() {
return (
<div>
<button onClick={this.someFunction}>Click</button>
</div>
);
}
}
ReactDOM.render(
<App />,
document.getElementById("app")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="app"></div>
So, you can't use this.state.handleChange since there is no handleChange in the state. It is a function belongs to the class. This is why we use this.handleChange.
you can store a function in state
constructor(super){
super(props)
this.state = {
generateANumber: () => this.setState({ number: Math.floor(Math.random() * 100) }),
number: 0
}
}
then if you want to call it in your render method
render() {
return <p> {this.state.number} <button onClick={() => this.state.generateANumber()} Press Me To Generate A New Number </button> </p>
}
This is the concept of storing a function in state. This.function just means the function belongs to that class so you can use it using the this keyword.

Why doesn't react component render when trying to render it in another components click function

I'm trying to render buttons to a page which when clicked render the hard-coded weather data to the page depending on the day that was clicked. The click function works fine and the buttons are rendered just as I expect them to, but when a button is clicked the Day component doesn't render.
I don't understand what I'm doing wrong since my code reaches the console.log in the click handler function. Then the handler function should render the component but for some reason it does not.
Here is my code:
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import myData from "./weather.json";
class Day extends React.Component {
constructor(props) {
super(props);
this.state = {
description: null
};
}
render() {
console.log("at last"); //this isn't reached
return (
<div className="dayWeather">
<div className="dayWeather">Humidity {this.props.humidity}</div>
<div className="dayWeather">Temperature {this.props.temperature}</div>
</div>
);
}
}
class DayRow extends React.Component {
constructor(props) {
super(props);
this.state = {
days: Array(7).fill(null)
};
this.handler = this.handler.bind(this);
this.renderDay = this.renderDay.bind(this);
}
handler(day) {
let daysWeather = myData[day];
console.log("now we've reached this far"); //this console log is reached when a button is clicked.
return (
<Day
humidity={daysWeather.humidity}
temperature={daysWeather.temperature}
/>
);
}
renderDay(day) {
return (
<div>
<button
className="day"
onClick={() => {
this.handler(day);
}}
>
{day}
</button>
</div>
);
}
render() {
return (
<div>
<div className="day-row">
{this.renderDay("Monday")}
{this.renderDay("Tuesday")}
{this.renderDay("Wednesday")}
{this.renderDay("Thursday")}
{this.renderDay("Friday")}
{this.renderDay("Saturday")}
{this.renderDay("Sunday")}
</div>
</div>
);
}
}
class Weather extends React.Component {
render() {
return (
<div className="weather">
<div className="weather-panel">
<DayRow />
</div>
<div className="day" />
</div>
);
}
}
// ========================================
ReactDOM.render(<Weather />, document.getElementById("root"));
Don't return UI elements in click handler as it won't render. Just set a flag inside handler and use it to display the Day component inside your render function.
class DayRow extends React.Component {
constructor(props) {
super(props);
this.state = {
days: Array(7).fill(null),
showDay: false
};
this.handler = this.handler.bind(this);
this.renderDay = this.renderDay.bind(this);
}
handler() {
this.setState({
showDay: true,
});
}
renderDayComponent(day) {
let daysWeather = myData[day];
console.log("now we've reached this far"); //this console log is reached when a button is clicked.
return (
<Day
humidity={daysWeather.humidity}
temperature={daysWeather.temperature}
/>
);
}
renderDay(day) {
return (
<div>
<button
className="day"
onClick={() => {
this.handler();
}}
>
{day}
</button>
{this.state.showDay && this.renderDayComponent(day)}
</div>
);
}
render() {
return (
<div>
<div className="day-row">
{this.renderDay("Monday")}
{this.renderDay("Tuesday")}
{this.renderDay("Wednesday")}
{this.renderDay("Thursday")}
{this.renderDay("Friday")}
{this.renderDay("Saturday")}
{this.renderDay("Sunday")}
</div>
</div>
);
}
}
My guess is that you are trying to render the day in the Weather compoment? To do this, you must keep some sort of state so that React knows what to render. Whenever you change the state, React will call render to re-render your compoment.
Therefore, since the state of wether or not a day is to be shown is local to the Weather component, you need to store the state there:
class Weather extends React.Component {
constructor(props) {
super(props)
this.state = { activeDay: undefined }
}
render() {
// Store the current dayData in a variable if an activeDay is chosen, it will be falsy if no day has been chosen.
const dayData = this.state.activeDay && myData[this.state.activeDay]
return (
<div className="weather">
<div className="weather-panel">
// DayRow will inform Weather when a day has been selected, i.e., clicked
<DayRow onSelected={day => this.setState({ activeDay: day })} />
</div>
<div className="day">
// This is where you are rendering the day, only if a day
// is active. I.e., dayData is truthy
{ dayData && <Day humitidy={dayData.humitidy} temperature={dayData.temperature} /> }
</div>
</div>
);
}
}
Your DayRow would simple communicate with Weather by saying which day is selected.
class DayRow extends React.Component {
constructor(props) {
super(props);
this.renderDay = this.renderDay.bind(this)
}
renderDay(day) {
return (
<div>
<button
className="day"
onClick={() => {
this.props.onSelected(day);
}}
>
{day}
</button>
</div>
);
}
render() {
return (
<div>
<div className="day-row">
{this.renderDay("Monday")}
{this.renderDay("Tuesday")}
{this.renderDay("Wednesday")}
{this.renderDay("Thursday")}
{this.renderDay("Friday")}
{this.renderDay("Saturday")}
{this.renderDay("Sunday")}
</div>
</div>
);
}
}
Returning JSX from your event handler will not render it. You have to do all rendering in your component's render method.
You could instead have an additional object in your state that keep track of if the day has been clicked or not, and use that state in your rendering.
class DayRow extends React.Component {
constructor(props) {
super(props);
this.state = {
days: Array(7).fill(null),
showDays: {}
};
this.handler = this.handler.bind(this);
this.renderDay = this.renderDay.bind(this);
}
handler(day) {
this.setState(previousState => {
const showDays = { ...previousState.showDays };
showDays[day] = !showDays[day];
return { showDays };
});
}
renderDay(day) {
let daysWeather = myData[day];
return (
<div>
<button
className="day"
onClick={() => {
this.handler(day);
}}
>
{day}
</button>
{this.state.showDays[day] && (
<Day
humidity={daysWeather.humidity}
temperature={daysWeather.temperature}
/>
)}
</div>
);
}
// ...
}
The returned component from handler function is being passed to onClick event. Its not getting into the DOM tree.
You can change the code as shown below.
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import myData from './weather.json';
const myData =
class Day extends React.Component {
constructor(props) {
super(props);
this.state = {
description: null
};
}
render() {
console.log('at last'); //this isn't reached
return (
<div className="dayWeather">
<div className="dayWeather">Humidity {this.props.humidity}</div>
<div className="dayWeather">Temperature {this.props.temperature}</div>
</div>
);
}
}
class DayRow extends React.Component {
constructor(props) {
super(props);
this.state = {
days: Array(7).fill(null)
};
this.handler = this.handler.bind(this);
this.renderDay = this.renderDay.bind(this);
}
handler(day) {
let daysWeather = myData[day];
console.log('now we\'ve reached this far'); //this console log is reached when a button is clicked.
this.props.handler(daysWeather);
}
renderDay(day) {
return (
<div>
<button
className="day"
onClick={() => {
this.handler(day);
}}
>
{day}
</button>
</div>
);
}
render() {
return (
<div>
<div className="day-row">
{this.renderDay('Monday')}
{this.renderDay('Tuesday')}
{this.renderDay('Wednesday')}
{this.renderDay('Thursday')}
{this.renderDay('Friday')}
{this.renderDay('Saturday')}
{this.renderDay('Sunday')}
</div>
</div>
);
}
}
class Weather extends React.Component {
constructor(props){
super(props);
this.state = {
selectedDay: {}
};
this.handler = this.handler.bind(this);
}
handler(day){
this.setState({
selectedDay: day
});
}
render() {
let day = null;
if(Object.keys(this.state.selectedDay) > 0){
day = <Day
humidity={selectedDay.humidity}
temperature={selectedDay.temperature}
/>;
}
return (
<div className="weather">
<div className="weather-panel">
<DayRow onDayChange={this.handler}/>
</div>
<div className="day">
{day}
</div>
</div>
);
}
}
// ========================================
ReactDOM.render(<Weather />, document.getElementById('root'));

React - How to pass props to a component passed as prop

I have a React component (React v15.5.4) that you can pass other components to:
class CustomForm extends React.Component {
...
render() {
return (
<div>
{this.props.component}
</div>
);
}
}
And I have a different component that uses it:
class SomeContainer extends React.Component {
...
render() {
let someObjectVariable = {someProperty: 'someValue'};
return (
<CustomForm
component={<SomeInnerComponent someProp={'someInnerComponentOwnProp'}/>}
object={someObjectVariable}
/>
);
}
}
Everything renders fine, but I want to pass someObjectVariable prop to the child component inside CustomForm (in this case that'll be SomeInnerComponent), since in the actual code you can pass several components to it instead of just one like the example.
Mind you, I also need to pass SomeInnerComponent its own props.
Is there a way to do that?
You can achieve that by using React.cloneElement.
Like this:
class CustomForm extends React.Component {
...
render() {
return (
<div>
{React.cloneElement(this.props.component,{ customProps: this.props.object })}
</div>
);
}
}
Working Code:
class Parent extends React.Component{
render() {
return(
<Child a={1} comp={<GChild/>} />
)
}
}
class Child extends React.Component{
constructor(){
super();
this.state = {b: 1};
this.updateB = this.updateB.bind(this);
}
updateB(){
this.setState(prevState => ({b: prevState.b+1}))
}
render(){
var Comp = this.props.comp;
return (
<div>
{React.cloneElement(Comp, {b: this.state.b})}
<button onClick={this.updateB}>Click to update b</button>
</div>
);
}
}
const GChild = props => <div>{JSON.stringify(props)}</div>;
ReactDOM.render(
<Parent />,
document.getElementById('container')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id='container' />
You can do in the same as you did for SomeInnerComponent.
Just pass named props.
Inside CustomForm,
render() {
const MyComponent = this.props.component; //stored it in some variable
return (
<div>
<MyComponent customProps = {this.props.object} /> //access object here and passed it or passed individual props
</div>
);
}
EDIT :
Please find the working demo here.
You have a couple of options to achieve what your asking.
class SomeContainer extends React.Component {
...
render() {
let someObjectVariable = {someProperty: 'someValue'};
return (
<CustomForm
component={<SomeInnerComponent propFromParent={someObjectVariable}/>}
object={someObjectVariable}
/>
);
}
}
Or you can clone the component prop and apply the new props as Mayank said. In your case
class CustomForm extends React.Component {
...
render() {
return (
<div>
{React.cloneElement(this.props.component,
{propFromParent:this.props.someObjectVariable})}
</div>
);
}
}
You can use react-overrides for this.
Create CustomForm:
import o from "react-overrides";
const InnerComponent = () => null; // default
class CustomForm extends React.Component {
...
render() {
return (
<div>
<InnerComponent {...o} />
</div>
);
}
}
Pass props and component of InnerComponent at overrides prop:
class SomeContainer extends React.Component {
...
render() {
let someObjectVariable = {someProperty: 'someValue'};
return (
<CustomForm
object={someObjectVariable}
overrides={{
InnerComponent: {
component: SomeInnerComponent,
props: {
someProp: 'someInnerComponentOwnProp'
}
}
}}
/>
);
}
}
<TextField place={"India"}> </TextField>
and in your component TextField
class TextField extends Component {
constructor(props){
super(props);
}
render() {
return (
<div>
<input />
<button> {this.props.place} </button>
</div>
)
}
}
i think what you are trying to achieve is something like this you have to pass your InnerComponent as an arrow function () => ..
class SomeContainer extends React.Component { ... render() {
let someObjectVariable = {someProperty: 'someValue'};
return (
<CustomForm
component={() => <SomeInnerComponent someProp={'someInnerComponentOwnProp'}/>}
object={someObjectVariable}
/>
); } }

My component doesn't rerender

When I click on button, I see '1' in console, but never see '2'. Why it happens? Can you help me please to resolve this issue? I realy dont know why my second component doesn't update.
class App extends PureComponent {
constructor() {
super();
this.state = {
name: 'Vasya'
}
this._onChange = this._onChange.bind(this);
}
_onChange(name) {
this.setState({
name: name
});
}
render() {
console.log(1);
return {
<div>
<Button onClick={this._onChange('Petr')} />
<AnotherComponent username={this.state.name} />
</div>
}
}
}
class AnotherComponent extends PureComponent {
const {
username
} = this.props
render() {
console.log(2);
return {
<div>
test
</div>
}
}
}
export default App;
A few code problems in your example!
when you return your React elements from render(), they must be wrapped in parens () not curlies {}
use React.Component, not React.PureComponent, or you'll get issues
<Button> isn't a thing, use <button>
The main problem then is an infinite loop - when you render, this line:
<Button onClick={this._onChange('Petr')} />
...this calls the _onChange() function at render time and passes the result to Button as the onClick prop. This isn't what you want - you want the onClick prop to be a function that calls _onChange(). So
<button onClick={ () => this._onChange('Petr')} />
Full working example:
class App extends React.Component {
constructor() {
super();
this.state = {
name: 'Vasya'
}
this._onChange = this._onChange.bind(this);
}
_onChange(name) {
this.setState({
name: name
});
}
render() {
console.log(1);
return (
<div>
<button onClick={ () => this._onChange("Petr") } />
<AnotherComponent username={this.state.name} />
</div>
);
}
}
class AnotherComponent extends React.Component {
render() {
console.log(2);
return (
<div>
test
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("app") );
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="app"></div>

Categories

Resources