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>
)
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 component which holds state that maintains what is being typed in an input box. However, I am unable to type anything in my input box. The input box is located in my child component, and the onChange and value of the input box is stored in my parent component.
Is there any way I can store all the form logic/input data on my parent component and just access it through my child components?
Here is a section of my parent component code:
export class Search extends React.Component {
constructor(props) {
super(props);
this.state = {
value: '',
screenType: 'init',
series: [],
isLoading: true,
title: '',
};
this.handleChange = this.handleChange.bind(this);
this.searchAPI = this.searchAPI.bind(this);
this.clickSeries = this.clickSeries.bind(this);
}
handleChange(e) {
this.setState({
value: e.target.value,
});
}
onKeyPress = (e) => {
if (e.which === 13) {
this.searchAPI();
}
};
async searchAPI() {
...some search function
}
render() {
return (
<Init onKeyPress={this.onKeyPress} value={this.state.value} onChange={this.handleChange} search={this.searchAPI} />
);
}
And here is a section of my Child component:
function Init(props) {
return (
<div className="container">
<div className="search-container-init">
<input
onKeyPress={props.onKeyPress}
className="searchbar-init"
type="text"
value={props.value}
onChange={props.handleChange}
placeholder="search for a TV series"></input>
<button className="btn-init" onClick={props.search}>
search
</button>
</div>
</div>
);
}
export default Init;
Incorrect function name used in the child. onChange prop passed into child in the parent and using that in the child as handleChange.
Also, you do not need to bind explicitly if using ES6 function definition.
Here is the updated code:
Search.js
export class Search extends React.Component {
constructor(props) {
super(props);
this.state = {
value: '',
screenType: 'init',
series: [],
isLoading: true,
title: '',
};
}
const handleChange = (e) => {
this.setState({
value: e.target.value,
});
}
const onKeyPress = (e) => {
if (e.which === 13) {
this.searchAPI();
}
};
const async searchAPI = () => {
...some search function
}
render() {
return (
<Init onKeyPress={this.onKeyPress} value={this.state.value} handleChange={this.handleChange} search={this.searchAPI} />
);
}
Child component:
function Init(props) {
return (
<div className="container">
<div className="search-container-init">
<input
onKeyPress={props.onKeyPress}
className="searchbar-init"
type="text"
value={props.value}
onChange={(e) => props.handleChange(e)}
placeholder="search for a TV series"></input>
<button className="btn-init" onClick={props.search}>
search
</button>
</div>
</div>
);
}
export default Init;
In your child component, you are using props.handleChange! But in your parent component, you passed it as onChange! Use the same name you used to pass the value/func. It should be like props.onChange! A silly error to watch out for
I'm currently following this and I did get it to work. But I would like to know if there is a way to stop the Query Render from reloading the data when calling this.setState(). Basically what I want is when I type into the textbox, I don't want to reload the data just yet but due to rendering issues, I need to set the state. I want the data to be reloaded ONLY when a button is clicked but the data will be based on the textbox value.
What I tried is separating the textbox value state from the actual variable passed to graphql, but it seems that regardless of variable change the Query will reload.
Here is the code FYR.
const query = graphql`
query TestComponentQuery($accountId: Int) {
viewer {
userWithAccount(accountId: $accountId) {
name
}
}
}
`;
class TestComponent extends React.Component{
constructor(props){
super(props);
this.state = {
accountId:14,
textboxValue: 14
}
}
onChange (event){
this.setState({textboxValue:event.target.value})
}
render () {
return (
<div>
<input type="text" onChange={this.onChange.bind(this)}/>
<QueryRenderer
environment={environment}
query={query}
variables={{
accountId: this.state.accountId,
}}
render={({ error, props }) => {
if (error) {
return (
<center>Error</center>
);
} else if (props) {
const { userWithAccount } = props.viewer;
console.log(userWithAccount)
return (
<ul>
{
userWithAccount.map(({name}) => (<li>{name}</li>))
}
</ul>
);
}
return (
<div>Loading</div>
);
}}
/>
</div>
);
}
}
Okay so my last answer didn't work as intended, so I thought I would create an entirely new example to demonstrate what I am talking about. Simply, the goal here is to have a child component within a parent component that only re-renders when it receives NEW props. Note, I have made use of the component lifecycle method shouldComponentUpdate() to prevent the Child component from re-rendering unless there is a change to the prop. Hope this helps with your problem.
class Child extends React.Component {
shouldComponentUpdate(nextProps) {
if (nextProps.id === this.props.id) {
return false
} else {
return true
}
}
componentDidUpdate() {
console.log("Child component updated")
}
render() {
return (
<div>
{`Current child ID prop: ${this.props.id}`}
</div>
)
}
}
class Parent extends React.Component {
constructor(props) {
super(props)
this.state = {
id: 14,
text: 15
}
}
onChange = (event) => {
this.setState({ text: event.target.value })
}
onClick = () => {
this.setState({ id: this.state.text })
}
render() {
return (
<div>
<input type='text' onChange={this.onChange} />
<button onClick={this.onClick}>Change ID</button>
<Child id={this.state.id} />
</div>
)
}
}
function App() {
return (
<div className="App">
<Parent />
</div>
);
}
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>
)
}
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})
}
}