I'm having some problems when I try to update all childs states from one of the child, here is an example of my code. The idea is to autoupdate all components from one of them.
I'm new in react, I have only been using for a week, so probably all this is a misunderstanding.
https://codesandbox.io/s/430qwoo94
import React from 'react';
import { render } from 'react-dom';
import Hello from './Hello';
class Parent extends React.Component {
constructor(props) {
super(props);
this.state = {
filedStr: 'some text',
fieldObj: {
field1: true,
field2: true
}
}
}
updObj = (which, val) => {
this.setState(prevState => ({
fieldObj: {
...prevState.fieldObj,
[which]: val,
},
}));
};
render() {
return (
<div>
<h2>Parent</h2>
Value in Parent Component State: {this.state.fieldObj.field1 ? 1 : 0} : {this.state.fieldObj.field2 ? 1 : 0}
<br />
<Child obj={this.state.fieldObj} onUpdate={this.updObj} />
<br />
<Child obj={this.state.fieldObj} onUpdate={this.updObj} />
<br />
<Child obj={this.state.fieldObj} onUpdate={this.updObj} />
</div>
)
}
}
class Child extends React.Component {
constructor(props) {
super(props);
this.state = {
obj: props.obj
}
}
update = (which) => {
this.props.onUpdate(which, !this.state.obj[which]);
this.setState(prevState => ({
obj: {
...prevState.obj,
[which]: !prevState.obj[which],
},
}));
};
render() {
return (
<div>
<h4>Child</h4>
Value in Child State: {this.state.obj.field1 ? 1 : 0} : {this.state.obj.field2 ? 1 : 0}<br />
<button type="button" onClick={(e) => { this.update('field1') }}>field1</button>
<button type="button" onClick={(e) => { this.update('field2') }}>field2</button>
</div>
)
}
}
render(<Parent />, document.getElementById('root'));
When all child components values are directly derivable from the props you do not need to create a state in child which is a replica of props and maintain it, what you need to do is modify the parent's state directly like
import React from 'react';
import { render } from 'react-dom';
import Hello from './Hello';
class Parent extends React.Component {
constructor(props) {
super(props);
this.state = {
filedStr: 'some text',
fieldObj: {
field1: true,
field2: true
}
}
}
updObj = (which, val) => {
this.setState(prevState => ({
fieldObj: {
...prevState.fieldObj,
[which]: val,
},
}));
};
render() {
return (
<div>
<h2>Parent</h2>
Value in Parent Component State: {this.state.fieldObj.field1 ? 1 : 0} : {this.state.fieldObj.field2 ? 1 : 0}
<br />
<Child obj={this.state.fieldObj} onUpdate={this.updObj} />
<br />
<Child obj={this.state.fieldObj} onUpdate={this.updObj} />
<br />
<Child obj={this.state.fieldObj} onUpdate={this.updObj} />
</div>
)
}
}
class Child extends React.Component {
constructor(props) {
super(props);
this.state = {
obj: props.obj
}
}
update = (which) => {
this.props.onUpdate(which, !this.props.obj[which]);
};
render() {
return (
<div>
<h4>Child</h4>
Value in Child State: {this.props.obj.field1 ? 1 : 0} : {this.props.obj.field2 ? 1 : 0}<br />
<button type="button" onClick={(e) => { this.update('field1') }}>field1</button>
<button type="button" onClick={(e) => { this.update('field2') }}>field2</button>
</div>
)
}
}
render(<Parent />, document.getElementById('root'));
CodeSandbox
However if you want to why your way of handling doesn't work as expected it, is because, you are not updating the state of the child components based on the state update in the parent, you were only setting it once in the constructor which is only called once when the component mounts, what you need is to implement the componentWillReceiveProps lifecycle function
Here, I've updated your code to meet your need -https://codesandbox.io/s/llnzm2y95z
Your assumption of child re-rendering is wrong. When the child rerenders the constructor method is not called in other words, constructor is only called once. To use next props and change in states you need to make use of the renders and componentWillReceiveProps. See react-component lifecycle http://busypeoples.github.io/post/react-component-lifecycle/
The problem is when you updated the parent's state using onClick={(e) => { this.update('field1') }} and onClick={(e) => { this.update('field1') }}
You updated the parent's state and this state was again passed to the child. But in the child you are not using this new props. You' re instead using the state, this state was updated only in the constructor, which is not updated after the new props got received. (As the constructor gets called only once)
One way to handle the new props is directly using the props in the render as the component will rerender and the updated props will be available to it.
The other way if you want to make use of the state, then update the state inside componentWillReceiveProps. (I would also want to point out that it is highly not-recommended to do setState inside a componentWillReceiveProps and componentDidMount). So better use the first step.
componentWillReceiveProps(newProps) {
if(newProps !== this.props){
this.setState({newStateObjects})
}
}
Related
How to update state of one component in another in react class component.
I have two class in reacts.
MyComponent and MyContainer.
export default class MyContainer extends BaseComponent{
constructor(props: any) {
super(props, {
status : false,
nameValue :"",
contentValue : ""
});
}
componentDidMount = () => {
console.log(this.state.status);
};
save = () => {
console.log("Hello I am Save");
let obj: object = {
nameValue: this.state.nameValue, // here I am getting empty string
templateValue: this.state.contentValue
};
// API Call
};
render() {
return (
<div>
<MyComponent
nameValue = {this.state.nameValue}
contentValue = {this.state.contentValue}
></MyComponent>
<div >
<button type="button" onClick={this.save} >Save</button>
</div>
</div>
);
}
}
MyComponent
export default class MyComponent extends BaseComponent{
constructor(props: any) {
super(props, {});
this.state = {
nameValue : props.nameValue ? props.nameValue : "",
contentValue : props.contentValue ? props.contentValue : "",
status : false
}
}
componentDidMount = () => {
console.log("MOUNTING");
};
fieldChange = (id:String, value : String) =>{
if(id === "content"){
this.setState({nameValue:value});
}else{
this.setState({contentValue:value});
}
}
render() {
return (
<div>
<div className="form-group">
<input id="name" onChange={(e) => {this.fieldChange(e)}}></input>
<input id = "content" onChange={(e) => {this.fieldChange(e)}} ></input>
</div>
</div>
);
}
}
In MyComponent I have placed two input field where on change I am changing the state.
Save button I have in MyContainer. In save button I am not able to read the value of MyComponent. What is the best way to achieve that.
You should be updating your state in MyContainer for save to have visibility of the state changes. Each component gets its own state, which makes MyComponent's state unique to that of MyContainer. What you should be doing is keeping the state in your parent/container component, and then passing it down as props (rather than duplicating it in your child). To do this, move fieldChange up to the MyContainer function, and remove the duplicate nameValue and contentValue state within MyComponent. See code commennts for further details:
export default class MyContainer extends BaseComponent{
...
fieldChange = (id:String, value : String) =>{
if(id === "content"){
this.setState({nameValue: value});
} else {
this.setState({contentValue: value});
}
}
render() {
return (
<div>
<MyComponent
nameValue={this.state.nameValue}
contentValue={this.state.contentValue}
onFieldChange={this.fieldChange} /* <---- Pass the function down to `MyComponent` */
/>
...
</div>
);
}
}
Then in MyComponent, call this.props.onFieldChange:
export default class MyComponent extends BaseComponent{
// !! this constructor can be removed as no state is being initialized anymore !!
constructor(props: any) {
super(props);
// removed state as we're using the state from `MyContainer`
}
componentDidMount = () => {
console.log("MOUNTING");
};
render() {
return (
<div>
<div className="form-group">
<input id="name" onChange={(e) => {this.props.fieldChange(e)}} /> /* <--- Change to `this.props.fieldChange()`. `<input />` is a self-closing tag.
<input id = "content" onChange={(e) => {this.props.fieldChange(e)}} />
</div>
</div>
);
}
}
Some additional notes:
If your component doesn't use this.props.children, then you should call it as <MyComponent ... props ... /> not <MyComponent ... props ...></MyComponent>
Your if-statement in your fieldChange looks reversed and should be checking if(id === "name"). I'm assuming this is an error in your question.
You're only passing one argument to fieldChange in your example code. I'm again assuming this in an error in your question.
I have a parent stateful react component that has a function that will change when an html span is clicked within the child component. I want to pass that method to the child component and call it when the snap is clicked I then want to pass it back up to the parent component and updated the state based on what is passed back up. I am having trouble passing down the method and calling it within the child component...
parent component:
export default class App extends Component {
constructor(props) {
super(props)
this.state = {
dates: [],
workouts: [],
selectedDate: '',
selectedWorkouts: []
}
this.updateDateAndWorkouts = this.updateDateAndWorkouts.bind(this)
axios.defaults.baseURL = "http://localhost:3001"
}
updateDateAndWorkouts = () => {
console.log('clicked')
}
render() {
return (
<div>
<DateBar data={this.state.dates}/>
<ClassList workouts={this.state.selectedWorkouts} updateDate={this.updateDateAndWorkouts}/>
</div>
)
}
This is the child component:
export default function Datebar(props) {
return (
<div>
{props.data.map((day, index) => {
return (
<div key={index}>
<span onClick={props.updateDate}>
{day}
</span>
</div>
)
})}
</div>
)
}
What I want to happen is when the method is called in thechild component, it calls the method that was passed and pass the text within the span div...
You have to actually pass function to child component
export default class App extends Component {
constructor(props) {
super(props)
this.state = {
dates: [],
workouts: [],
selectedDate: '',
selectedWorkouts: []
}
this.updateDateAndWorkouts = this.updateDateAndWorkouts.bind(this)
axios.defaults.baseURL = "http://localhost:3001"
}
updateDateAndWorkouts = () => {
console.log('clicked')
}
render() {
return (
<div>
<DateBar data={this.state.dates} updateDate={this.updateDateAndWorkouts} />
<ClassList workouts={this.state.selectedWorkouts} updateDate={this.updateDateAndWorkouts}/>
</div>
)
}
You have to call that method in child component
props.updateDate()
export default function Datebar(props) {
return (
<div>
{props.data.map((day, index) => {
return (
<div key={index}>
<span onClick={props.updateDate()}>
{day}
</span>
</div>
)
})}
</div>
)
}
This is weird, simple checkbox component state changes, prop does not and UI does not update!
import React, { Component } from 'react';
import { Checkbox } from 'semantic-ui-react'
export default class UiCheckBox extends Component {
constructor(props) {
super(props);
this.state = {
name: '',
label: '',
checked: false
};
}
render() {
return (
<Checkbox label={this.props.label} name={this.props.name} checked={this.props.checked} onChange={this.handleChange.bind(this)} />
);
}
handleChange() {
this.setState({
checked: !this.state.checked
});
console.log("prop:" + this.props.checked);
console.log("state:" + !this.state.checked);
}
}
If i change
checked={this.props.checked}
To this
checked={this.state.checked}
It works, but I cant set the initial value of the checkbox, what am I doing wrong, PS i'm certain this was working earlier[honest] ?
This is how I'm using it.
<UiCheckBox name={"Tea"} label={"Tea"} checked={false} />
Idea's anyone ?
Thanks
Your handleChange() only changes this.state.checked. this.state.checked is not used in your render() method. Therefore, no visible change occurs.
If you want to change props: you'll have to pass a function that updates the original data store.
Else: you can set default state using props.
See below for a practical example 👇
// Check Box.
class CheckBox extends React.Component {
// State.
state = {checked: this.props.checked}
// Render.
render() {
const {checked} = this.state
return (
<React.Fragment>
<label>{`${checked}`}</label>
<input type="checkbox" checked={checked} onChange={this.toggle}/>
</React.Fragment>
)
}
// Toggle.
toggle = event => this.setState(state => ({checked: !state.checked}))
}
// Mount.
ReactDOM.render(<CheckBox checked={true}/>, document.querySelector('#root'))
<script crossorigin src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div id="root"></div>
I am trying to bind a method of a parent component to the state of its child component but I'm unable to get the desired result. I checked the value of 'this' in App component and it still points to the App component. Should it not be pointing to the ItemsList component since its being binded to it using bind()? Can someone please point out the mistake I'm making.
import React from 'react';
import {render} from 'react-dom';
class Item extends React.Component {
constructor(props) {
super(props);
}
render() {
return <div> {this.props.value} </div>;
}
}
class ItemList extends React.Component {
constructor(props) {
super(props);
this.state = {
itemArray: ['Work', 'Learn React']
}
this.props.adder.bind(this);
console.log(this.props.adder)
}
render() {
const items = this.state.itemArray.map(el=><Item key={el} value={el} />);
return (
<div>
<h2> To Do List </h2>
<ul>{items}</ul>
</div>
);
}
}
class App extends React.Component {
constructor(props) {
super(props);
}
addElement (data) {
let items = this.state.ItemList;
items.push(<Item value={data} />);
}
render() {
return (
<div>
<input type="text" ref={input=>this.input=input} />
<input type="button" value="Add" onClick={()=>this.addElement(this.input.value)}/>
<ItemList adder={this.addElement} />
</div>
);
}
}
render(<App />, document.getElementById('root'));
Should it not be pointing to the ItemsList component since its being binded to it using bind()?
Well,the step you following in not right one.
In App Component
You need to store the ItemList (child) component reference in App(parent) component.
<ItemList adder={this.addElement} bindChild = {(ref)=>this.itemList = ref}/>
In ItemList component,
you need to call bindChild method when ItemList component mounted.
componentDidMount(){
this.props.bindChild(this);
}
Now, in your App (parent) component, you have reference for ItemList (child) component in this.itemList property.
In App component, you can use this.itemList to update state of ItemList (child) component.
addElement(data) {
let items = this.itemList.state.itemArray;
console.log(items);
const newItem = <Item value={data} />
this.itemList.setState({ itemArray : [...items, newItem]})
}
Please check complete example on codesandbox
Though what you want is technically possible, this is a much more explicit easy to understand way to do it.
I re-factored your code so that the data flow only goes in one direction, from App to `Itemimport React from "react";
import { render } from "react-dom";
I also changed Item and ItemList to stateless components that take value and items as props respectively.
The main change is that App holds the state instead of ItemList
const Item = ({ value }) => <div>{value}</div>;
const ItemList = ({ items }) => (
<div>
<h2>To Do List</h2>
{items.map(item => <Item key={item} value={item} />)}
</div>
);
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
items: ["Work", "Learn React"]
};
}
addElement(value) {
this.setState(state => ({
items: [...state.items, value]
}));
}
render() {
return (
<div>
<input type="text" ref={input => (this.input = input)} />
<input
type="button"
value="Add"
onClick={() => this.addElement(this.input.value)}
/>
<ItemList items={this.state.items} />
</div>
);
}
}
render(<App />, document.querySelector("#root"));
Here is a CodeSandbox with your working app: https://codesandbox.io/s/4r4v0w5o94
I have a parent component, PlanList:
class PlanList extends Component {
constructor(props) {
super(props);
this.renderPlans = this.renderPlans.bind(this);
this.planListFilter = <PlanListFilter onChange={this.handleFilterChange.bind(this)} />
}
loadPlans() {
console.log(this.planListFilter);
// returns: Object {$$typeof: Symbol(react.element), key: null, ref: null, props: Object, _owner: ReactCompositeComponentWrapper…}
console.log(this.planListFilter.state);
// returns: undefined
// I expect it to return the state object i defined in the PlanListFilter constructor
// here I would apply the filters to the PlanTable
}
handleFilterChange(event) {
this.loadPlans();
}
render() {
return (
<div className="PlanList">
{this.planListFilter}
<PlanTable />
</div>
)
}
}
and a child component, PlanListFilter:
class PlanListFilter extends Component {
constructor(props) {
super(props);
this.state = {
search: '',
};
this.handleSearchChange = this.handleSearchChange.bind(this);
}
handleSearchChange(event) {
this.setState({search: event.target.value});
this.props.onChange(event);
}
render() {
return (
<FormControl type="text" placeholder="Search" onChange={this.handleSearchChange} value={this.state.search} />
);
}
}
When changing the text on the FormControl, the onChange property is fired as expected, but in the parent object, the state of the child is undefined. I expect it would be populated with the correct state.
In React data flows in one direction, if your parent should know about changes in the child, you have to pass a handler as a prop to the child, so it will be called from within the child.
class Papa extends React.Component {
constructor(p, c) { super(p, c) }
handleFilterChange(valueFromChild) {
//
}
render() {
return <Child filterHandler={this.handleFilterChange} />
}
}
const Child = ({filterHanlder}) => (
<button onClick={() => filterHandler('valueToParent') } >Click Me</button>
)