I want to be able to update a child component property from both the parent and itself according to the last event.
For example:
class MyParent extends Component {
state ={
text:"";
}
render() {
return (
<View>
<MyChild text={this.state.text} />
<Button
onPress={()=>this.setState({text:"parent"})}
title="Update From Parent"
/>
</View>
);
}
}
class MyChild extends Component {
state ={
text:"";
}
componentWillReceiveProps(nextProps) {
if (nextProps.text!== this.state.text) {
this.setState({text:nextProps.text});
}
}
render() {
return (
<View>
{/* I want that the text field will be updated from the last event*/}
<Text>{this.state.text}</Text>
<Button
onPress={()=>this.setState({text:"child"})}
title="Update From Child"
/>
</View>
);
}
}
The issue is that componentWillReceiveProps is triggered each time the setState is called so the text property takes the value from the parent and not from the child.
How can I achive this result?
Thanks a lot
Elad
Manage your state through parent component and pass the function that will update the state of parent component in child component
class MyParent extends Component {
constructor(props) {
super(props);
this.state = {
text: "",
updateParentState: (newState) => this.setState(newState)
}
}
render() {
let { text, updateParentState } = this.state;
return (
<View>
<MyChild data={{ text, updateParentState }} />
<Button
onPress={() => updateParentState({ text: "parent" })}
title="Update From Parent"
/>
</View>
);
}
}
class MyChild extends Component {
constructor(props) {
super(props);
this.state = {
text: props.data.text
}
}
componentWillReceiveProps(nextProps) {
if (nextProps.text !== this.state.text) {
this.setState({ text: nextProps.data.text });
}
}
render() {
let { updateParentState } = this.props.data;
return (
<View>
<Text>{this.state.text}</Text>
<Button
onPress={() => updateParentState({ text: "child" })}
title="Update From Child"
/>
</View>
);
}
}
You are missing this. in the setState call in child. please check if this is not the issue.
<Button
onPress={()=>setState({text:"child"})}
title="Update From Child"
/>
should be
<Button
onPress={()=>this.setState({text:"child"})}
title="Update From Child"
/>
Related
I have the following setup:
import {getNewImage} from '...'
export default class FirstClass extends Component {
constructor() {
super();
this.state = {
imageURL: 'www.test.com/new.jpg',
}
}
update = () => {
this.setState({
imageURL: 'www.test.com/updated.jpg',
})
}
render() {
return (
<View>
<Image
source={{ uri: this.state.imageURL }}
/>
</View>
);
}
}
import Class1 from '...'
export default class SecondClass extends Component {
render() {
return (
<TouchableOpacity onPress={() => new FirstClass().update()}>
<Class1></Class1>
</TouchableOpacity>
);
}
}
The problem is: it doesn't update the imageURL. I tried multiple things. Logging this inside of update gave back the right object. But trying to log the this.setState() gives an undefinded back.
I suppose by Class1 you mean FirstClass.
you should use the reference of the component using ref, not creating new instance of FirstClass class
checkout this code
export default class SecondClass extends Component {
private firstClass = null;
render() {
return (
<TouchableOpacity onPress={() => this.firstClass.update()}>
<Class1 ref={ref => this.firstClass = ref} />
</TouchableOpacity>
);
}
}
You can try to update state in Class1 similar to what do you want by the react reference.
But on my opinion this is non a good case and predictable behavior to change child component state from parent.
As another options you can add additional state to the SecondClass and pass it via props to child component. And inside Class1 in getDerivedStateFromProps based on that prop change state.
export default class FirstClass extends Component {
constructor() {
super();
this.state = {
isNeedToUdpate: false,
imageURL: "www.test.com/new.jpg"
};
}
static getDerivedStateFromProps(props, state) {
if (props.isNeedToUdpate !== state.isNeedToUdpate) {
return {
isNeedToUdpate: props.isNeedToUdpate,
imageURL: "www.test.com/updated.jpg"
};
}
return null;
}
render() {
return (
<View>
<Image source={{ uri: this.state.imageURL }} />
</View>
);
}
}
export default class SecondClass extends Component {
constructor() {
super();
this.state = {
isNeedToUpdateClass1: false
};
}
render() {
return (
<TouchableOpacity
onPress={() => {
this.setState({ isNeedToUpdateClass1: true });
}}
>
<Class1 isNeedToUpdate={this.state.isNeedToUpdateClass1}></Class1>
</TouchableOpacity>
);
}
}
I have two components and i am trying to pass array state on back button to parent component but its not showing any data.
Screen1.js
class Screen1 extends Component {
constructor(props) {
super(props);
this.state = {
selectedRecord: []
};
}
updateData = (data) => {
this.setState({selectedRecord: data})
}
render() {
const {container} = styles;
return (
<View style={container}>
<Button onPress={() => alert(this.state.selectedRecord)}>
Show Value
</Button>
</View>
);
}
}
I created a method on Screen2.js
class Screen2 extends Component {
constructor(props) {
super(props);
this.state = {
record: [1, 2, 3, 4, 5]
};
}
backStock() {
this.props.navigation.state.params.updateData(this.state.record);
this.props.navigation.goBack()
}
render() {
const {container} = styles;
return (
<View style={container}>
<Button onPress={() => this.backStock()} transparent>
<Icon name='arrow-back'/>
</Button>
</View>
);
}
}
And backStock() method runs when i click on my back button. But when i tried to print value in console it was empthy. Am i missing something, is there a smooth way to pass states onback method?
I have a problem with refs on React Native. This is a simplified version of my code:
class Main extends React.Component {
constructor(props) {
...
this.refs = {};
}
render() {
if(this.state.page=="index") {
return(
<View>
<FlatList ref={flatlist => this.refs.flatlist = flatlist}> ... </FlatList>
<MyActionButton flatlist={this.refs.flatlist}/>
</View>
)
} else if (this.state.page="text"=){
return(
<Text> ... </Text>
)
}
}
}
class MyActionButton extends React.Component {
render(
return(
<ActionButton>
<ActionButtonItem onPress={() => {
console.log("AB props", this.props)
}} />
</ActionButton>
)
)
}
The app starts with this.state.page = "index" so when I press MyActionButton I see the log as expected, and things seem to work:
'AB props', {flatlist: {A LOT OF STUFF HERE}}
However If I change the state.page to "text" and then come back to "index" again, when I press MyActionButton I get:
'AB props', {flatlist: undefined}
I'm not sure why that prop gets undefined and how to fix it to make it point to the actual FlatList.
I don't like very much, but I managed to get it working by changing the reference to a getter funcion
class Main extends React.Component {
constructor(props) {
...
this.refs = {};
}
getFlatList() {
return this.refs.flatlist;
}
render() {
if(this.state.page=="index") {
return(
<View>
<FlatList ref={flatlist => this.refs.flatlist = flatlist}> ... </FlatList>
<MyActionButton flatlist={this.getFlatList.bind(this)}/>
</View>
)
} else if (this.state.page="text"=){
return(
<Text> ... </Text>
)
}
}
}
I have a simple problem in React JS. I have two different click events, which switch the state of the component. The first one works perfectly, however I cannot get the second event to reset the component back to its original state. This is a stripped down version of my problem, so just know that I cannot move the click functions into the Child component.
class Parent extends Component{
constructor(){
this.state = {
open: false
}
this.handleOpen = this.handleOpen.bind(this)
this.handleClose = this.handleClose.bind(this)
}
handleOpen(){
this.setState({open: true})
}
handleClose(){
this.setState({open: false})
}
render(){
return(
<div>
<Child onOpen={this.handleOpen} onClose={this.handleClose} />
<Child onOpen={this.handleOpen} onClose={this.handleClose} />
<Child onOpen={this.handleOpen} onClose={this.handleClose} />
<Child onOpen={this.handleOpen} onClose={this.handleClose} />
</div>
)
}
}
Like I said, the handleOpen function switches the state, but the handleClose does not switch it back. I can get a console log to show on the handleClose function, so I know that it does not have to do with how it is being hooked up to the Child Component. Am I missing something about how to reset a state value after it has already been switched. Thank you for your help!
Here is How you have to do it!
class Child extends React.Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
console.log(this.props.isOpen);
if (this.props.isOpen) {
this.props.onClose();
} else {
this.props.onOpen();
}
}
render() {
return <button onClick={this.handleClick}>Click ME</button>;
}
}
class Parent extends React.Component{
constructor(props){
super(props);
this.state = {
open: false
}
this.handleOpen = this.handleOpen.bind(this)
this.handleClose = this.handleClose.bind(this)
}
handleOpen(){
this.setState({open: true})
}
handleClose(){
this.setState({open: false})
}
render(){
return(
<div>
<p>{this.state.open.toString()}</p>
<Child onOpen={this.handleOpen} onClose={this.handleClose} isOpen={this.state.open} />
<Child onOpen={this.handleOpen} onClose={this.handleClose} isOpen={this.state.open} />
<Child onOpen={this.handleOpen} onClose={this.handleClose} isOpen={this.state.open} />
<Child onOpen={this.handleOpen} onClose={this.handleClose} isOpen={this.state.open} />
</div>
)
}
}
ReactDOM.render(
<Parent/>,
document.getElementById('container')
);
I have a parent component and two child components. One of the child components is a form with an add button. I am able to capture the text when add button is pressed, but stuck when trying to pass that value to the parent component, update the array in parent component and re-render the view.
Parent Component:
var Tasks = ['Competitor Study','Content Plan','Write','Promote','Consumer Research']
//Build React Component
class MyTaskList extends Component {
render() {
return (
<View style={styles.container}>
<Text style={styles.titleStyle}>
My Tasks
{"\n"}
({Moment().format("MMM Do YY")})
{"\n"}
</Text>
<AddTask />
{this.workitems()}
</View>
);
}
workitems() {
return (Tasks.map(function(workitem,i) {
return <ListItem key={i} workitem={workitem} />
}
)
);
}
}
Child component with the form
class AddTask extends Component {
constructor(props) {
super(props);
this.state = {
enterTask: 'Enter task'
};
}
onTaskTextChanged(event) {
this.setState({ enterTask: event.nativeEvent.text });
}
onAddPress() {
var newtask = this.state.enterTask;
console.log('new task - '+newtask);
}
render() {
return (
<View style={styles.addTask}>
<TextInput
style={styles.taskInput}
value={this.state.enterTask}
onChange={this.onTaskTextChanged.bind(this)}
placeholder='Enter task'/>
<TouchableHighlight style={styles.button}
onPress={this.onAddPress.bind(this)}
underlayColor='#99d9f4'>
<Text style={styles.buttonText}>Add</Text>
</TouchableHighlight>
</View>
);
}
}
I would instead use state for this as using forceUpdate() is not recommended
From React docs:
Calling forceUpdate() will cause render() to be called on the component, skipping shouldComponentUpdate(). This will trigger the normal lifecycle methods for child components, including the shouldComponentUpdate() method of each child. React will still only update the DOM if the markup changes.
Normally you should try to avoid all uses of forceUpdate() and only read from this.props and this.state in render(). This makes your component "pure" and your application much simpler and more efficient.
(Based off of #1ven answer)
Parent Component:
//Build React Component
class MyTaskList extends Component {
constructor(props) {
super(props);
this.state = {
tasks: [
'Competitor Study',
'Content Plan',
'Write','Promote',
'Consumer Research'
]
}
}
handleAddTask(task) {
var newTasks = Object.assign([], this.state.tasks);
newTasks.push(task);
this.setState({tasks: newTasks});
}
render() {
return (
<View style={styles.container}>
<Text style={styles.titleStyle}>
My Tasks
{"\n"}
({Moment().format("MMM Do YY")})
{"\n"}
</Text>
<AddTask onAddTask={this.handleAddTask.bind(this)} />
{this.workitems()}
</View>
);
}
workitems() {
return this.state.tasks.map(function(workitem,i) {
return <ListItem key={i} workitem={workitem} />
});
}
}
Child component with the form
class AddTask extends Component {
constructor(props) {
super(props);
this.state = {
enterTask: 'Enter task'
};
}
onTaskTextChanged(event) {
this.setState({ enterTask: event.nativeEvent.text });
}
onAddPress() {
var newtask = this.state.enterTask;
console.log('new task - '+newtask);
// Providing `newtask` variable to callback.
this.props.onAddTask(newtask);
}
render() {
return (
<View style={styles.addTask}>
<TextInput
style={styles.taskInput}
value={this.state.enterTask}
onChange={this.onTaskTextChanged.bind(this)}
placeholder='Enter task'/>
<TouchableHighlight style={styles.button}
onPress={this.onAddPress.bind(this)}
underlayColor='#99d9f4'>
<Text style={styles.buttonText}>Add</Text>
</TouchableHighlight>
</View>
);
}
}
You should provide handleAddTask callback from parent to child component:
var Tasks = ['Competitor Study','Content Plan','Write','Promote','Consumer Research']
//Build React Component
class MyTaskList extends Component {
handleAddTask(task) {
// When task will be added, push it to array
Tasks.push(task);
}
render() {
return (
<View style={styles.container}>
<Text style={styles.titleStyle}>
My Tasks
{"\n"}
({Moment().format("MMM Do YY")})
{"\n"}
</Text>
<AddTask onAddTask={this.handleAddTask} />
{this.workitems()}
</View>
);
}
workitems() {
return (
Tasks.map(function(workitem,i) {
return <ListItem key={i} workitem={workitem} />
})
);
}
}
Next, you should pass task from child component to this callback:
class AddTask extends Component {
constructor(props) {
super(props);
this.state = {
enterTask: 'Enter task'
};
}
onTaskTextChanged(event) {
this.setState({ enterTask: event.nativeEvent.text });
}
onAddPress() {
var newtask = this.state.enterTask;
console.log('new task - '+newtask);
// Providing `newtask` variable to callback.
this.props.onAddTask(newtask);
}
render() {
return (
<View style={styles.addTask}>
<TextInput
style={styles.taskInput}
value={this.state.enterTask}
onChange={this.onTaskTextChanged.bind(this)}
placeholder='Enter task'/>
<TouchableHighlight style={styles.button}
onPress={this.onAddPress.bind(this)}
underlayColor='#99d9f4'>
<Text style={styles.buttonText}>Add</Text>
</TouchableHighlight>
</View>
);
}
}
That's it. Hope, it helps!