React Native Make State Change From different component - reactjs

I am using react native and I have a situation where I navigate to a new component using react navigation v2 stack navigator. The user then presses an option and goes back to the original screen they were at with updated information.
My question is how do change the state of the previous screen so it shows the information the user selected?
ShowFruitPage.js
This page shows a list of fruits that the user picked.
export default class ShowFruitPage extends Component{
constructor(){
super();
this.state = {
List: [{Fruit: apple}]
}
}
render(){
return(
<View style={styles.ViewStyle}>
<FlatList
data={this.state.List}
renderItem={({item}) =>
<TouchableNativeFeedback
background={TouchableNativeFeedback.Ripple('grey')}
onPress={() => this.props.navigation.navigate('AddFruit',
{
List: this.state.List
})}
<View style={styles.ListView}>
<Text>{item.Fruit}</Text>
</View>
</TouchableNativeFeedback>
}
/>
</View>
)
}
}
AddFruit.js
This page shows a list of available fruits the user can pick.
When the user picks from this list I want to update the list on the ShowFruitPage.
export default class AddFruit extends Component{
constructor(){
super();
this.state = {
FruitList: [{Fruit:orange}, {Fruit: pear}]
}
this.pickFruit = this.pickFruit.bind(this);
}
pickFruit(Fruit){
//Add the picked fruit to the ShowFruitPage state List
//Then Navigate back to ShowFruitPage
this.props.navigation.navigate('ShowFruitPage')
}
render(){
return(
<View style={styles.ViewStyle}>
<FlatList
data={this.state.FruitList}
renderItem={({item}) =>
<TouchableNativeFeedback
background={TouchableNativeFeedback.Ripple('grey')}
onPress={() => this.pickFruit(item)}
<View style={styles.ListView}>
<Text>{item.Fruit}</Text>
</View>
</TouchableNativeFeedback>
}
/>
</View>
)
}
}

Just as you did in ShowFruitPage.js with List, you could navigate back with some state, i.e.
this.props.navigation.navigate('ShowFruitPage', { Fruit });
to make { Fruit } available on navigation.state.params when you navigate back to ShowFruitPage.
Another possibility (I think) is to provide a callback when you navigate to AddFruit that can set the data on the ShowFruitPage allowing you to then just call navigation.goBack():
setFruit = fruit => {
this.setState({ FruitList: [...this.state.FruitList, fruit] });
}
this.props.navigation.navigate('AddFruit', { setFruit: this.setFruit });
And then:
pickFruit(Fruit){
const { navigation } = this.props;
navigation.state.params.setFruit(Fruit);
navigation.goBack();
}
An alternative and heavier solution, would be to implement some state management such as redux so that the data becomes independent of the individual page components.

Related

Updating changed value on user interface

I want my value to change on the screen when button is pressed. It does change the variable value behind the scenes but has no effect for the outdated value shown on the screen.
export default class App extends Component {
render() {
this.state = {
myVariable: 'egs'
}
const changeValue = () => {
this.state.myVariable = "CHANGED??!!"
}
return (
<View style={styles.container}>
<Text>
{this.state.myVariable}
</Text>
<Button onPress={changeValue} title="CHANGE IT"/>
</View>
);
}
}
I expect to update value to the changed one instead of outdated one.
Move state initialization outside of render as well as the changeValue method
You also cannot mutate statue directly, instead use setState()
This should work:
export default class App extends Component {
state = {
myVariable: 'egs'
}
changeValue = () => {
this.setState({myVariable:"CHANGED??!!"})
}
render() {
return (
<View style={styles.container}>
<Text>
{this.state.myVariable}
</Text>
<Button onPress={changeValue} title="CHANGE IT"/>
</View>
);
}
}
this.state.myVariable = "CHANGED??!!"
change to
this.setState({ myVariable: "CHANGED??!!" })

React does not rerender when a prop changes

I'm trying to develop an edit mode for a application.
In edit mode some buttons should have a lower opacity.
A boolean variable stores if the edit mode is active. This variable is passed down to its childs using props. If I now change the editMode in the parents state, the childs are not being rerendered.
Parentclass:
export default class Parentclass extends Component{
constructor(props){
super(props);
this.state = {
editMode: false,
};
}
render(){
return(
<View>
<EditButtonClass onEditPress={() => this.editButtonPress()}/>
<View>
<Subclass editMode={this.state.editMode}/>
</View>
</View>
);
}
editButtonPress(){
this.setState({editMode: true})
}
}
Subclass:
export default class Subclass extends Component{
render(){
return(
<View>
<Finalsubclass editMode={this.props.editMode}/>
</View>
);
}
}
Final subclass:
export default class Finalsubclass extends Component{
createStyle(){
return{
opacity: this.props.editMode ? 0.5 : 1,
}
}
render(){
return(
<TouchableOpacity style={this.createStyle()}/>
);
}
}
The button:
<TouchableOpacity onPress={() => this.props.onEditPress()}>
<Image source={require('../res/edit-button.png')} style=styles.editButton}/>
</TouchableOpacity>
The editMode in props does change. If I click on one of the buttons they're getting brighter. But not directly if I enable editmode.
Whats the best way to achieve a full rerendering?
you can user react lifecycle to re-render component
https://reactjs.org/docs/react-component.html
and for above issue you can use
componentWillReceiveProps(nextProps) {
...
}
The solution was to build a View around the TouchableOpacity and applying the styles to the view.
As componentWillReceiveProps is deprecated, i would suggest using componentDidUpdate.
To answer the question in your comment, you need to check the prevProps with the new ones to not get an infinite loop.
For example:
componentDidUpdate = (prevProps) => {
if(prevProps!==this.props){ /if the previous props are different from the current props
//do what you need
}
}
As it is an object, if you need to only check a single variable you can simply do:
if (prevProps.foo !== this.props.foo) {
//do what you need
}

React Native binding functions over .map()

So I am having some trouble combining concepts of .map() and function binding. I am using .map() in the same way ngFor is used in angular, to place a custom button component on the page for every item in a user's account.
Here is some example code:
class MyButton extends Component {
constructor(props) {
super();
this.state = {
progress: 0
}
}
render() {
return(
<TouchableWithoutFeedback onPress={this.pressFunction}>
(...more code inside)
</TouchableWithoutFeedback>
)
}
pressFunction = () => {
(animate progress from 0 to 1 for some animation)
}
}
/////////////////////////////////////////////////////////////////
class Parent extends Component {
render() {
return(
{
this.props.data.array.map(obj => {
return(
<View style={someStyle}>
<MyButton data={obj} />
</View>
)
})
}
)
}
}
So in the Parent Component, multiple MyButtons are rendered properly, each according to the passed object from the array. However, when any button is pressed, all of the pressFunctions for all MyButtons fire.
My question is I guess, how do I ensure that each pressFunction of each MyButton is bound only to the specific instance of the MyButton? I am having trouble with the scope here.
My understanding is that
functionName = () => {}
should properly bind the function to the instance, but I have tried the older ways as well with the same result.
I solved this by creating a dynamic ref on each object mapped to a MyButton, using a unique property of each obj in the array:
this.props.data.array.map(obj => {
return(
<View style={someStyle}>
<MyButton ref={obj.name} data={obj} />
</View>
)
})
Still don't know why my it didn't bind uniquely without a ref
You should pass onPress as a props. Below is the updated code
class MyButton extends Component {
constructor(props) {
super();
this.state = {
progress: 0
}
}
render() {
return(
<TouchableWithoutFeedback onPress={this.props.onPress}>
(...more code inside)
</TouchableWithoutFeedback>
)
}
}
/////////////////////////////////////////////////////////////////
class Parent extends Component {
pressFunction = () => {
(animate progress from 0 to 1 for some animation)
}
render() {
return this.props.data.array.map(obj => {
return(
<View style={someStyle}>
<MyButton
data={obj}
onPress={this.pressFunction}
/>
</View>
)
})
}
}

Appended React Native component not rerendering

I'm attempting to write a function to remove a React Native component (named "Card") from the DOM on-click, then append a new "Card" of the same class with different properties. For example, both Cards have background colors. If the first Card is green, the second Card, which should have a blue background, will inherit the green background of the original Card.
The Cards receive their background color passed as props, like so:
class Card extends Component {
constructor(props) {
super(props);
this.state = {
style: {
backgroundColor: this.props.card.backgroundColor
}
};
}
render() {
return (
<TouchableHighlight style={this.state.style}>
<Image source={this.props.card.img} />
</TouchableHighlight>
)
}
}
The main component looks like this:
class SetProject extends Component {
constructor(props) {
super(props);
this.state = {
cardArray: [{backgroundColor: 'green', img: require('~/SetProject/cardImages/ovals/1-red-empty-oval.png')}]
}
}
removeCard(){
let emptyArray = [];
this.setState({cardArray: emptyArray});
}
changeCard(){
// let emptyArray = [];
// this.setState({cardArray: emptyArray});
let newCardArray = [{backgroundColor: 'red', img: require('~/SetProject/cardImages/ovals/1-purple-shaded-oval.png')}]
this.setState({cardArray: newCardArray});
}
render() {
let cardElementArray = this.state.cardArray.map(theCard => {
return (
<Card card={theCard}></Card>
);
});
return (
<View>
<View>
{cardElementArray}
</View>
<TouchableHighlight>
<Text onPress={this.removeCard.bind(this)}>Remove Card</Text>
</TouchableHighlight>
<TouchableHighlight>
<Text onPress={this.changeCard.bind(this)}>Change Background</Text>
</TouchableHighlight>
</View>
);
}
}
So I've got two buttons: removeCard, which works great, and changeCard. If I press "Remove Card" and then press "Change Card," I see the exact results I'm looking for. The card is removed and is replaced by a new one. However, if I comment in these lines in changeCard:
// let emptyArray = [];
// this.setState({cardArray: emptyArray});
and press "Change Card" without pressing "Remove Card," the new Card has a new image but it keeps the background color of the previous Card. This also happens if I call this.removeCard() from changeCard.
In summary, I'd like to be able to perform the behavior of both of these functions simultaneously, but I'm only able to remove a Card and add a new, correctly rendered Card if I press both buttons separately.
Any ideas would be much appreciated!
Here you're using props for setting image but not setting style. You can use props as well. You have set the style in constructor. Then you want to change style but constructor is not called again but creating a new object.
You can use props setting styl as well
render() {
return (
<TouchableHighlight style={this.props.card.style}>
<Image source={this.props.card.img} />
</TouchableHighlight>
)
}
For better implementation in case properties of card gets more complex add an id property to card. You can use componentWillReceiveprops by this way unnecessary renders are neglected as well.
[{id:'1', backgroundColor: 'red', img:
require('~/SetProject/cardImages/ovals/1-purple-shaded-oval.png')}]
class Card extends Component {
constructor(props) {
super(props);
this.state = {
style: {
card: this.props.card
}
};
}
componentWillReceiveProps(nextProps){
if(nextProps.card.id != this.state.card.id)
{
setState({card:nextProps.card})
}
}
render() {
return (
<TouchableHighlight style={this.state.style}>
<Image source={this.props.card.img} />
</TouchableHighlight>
)
}
}
https://facebook.github.io/react/docs/react-component.html#componentwillreceiveprops
Don't you get warning about missing keys in array? Use unique identifier (or it's index in array as last resort) for each card, and use it to set key prop on each item in array. This way, when card in array changes, react can re-render it, because it's a new card to it.
let cardElementArray = this.state.cardArray.map(theCard => {
return (
<Card key={theCard.id} card={theCard}></Card>
);
});
Read more about keys here in React docs.

How to make simple button navigation to other view?

I am trying to make simple button navigation from one view to other ( I'm yet unsure that does this is called view or not ).
For example I have Featured.js as
var SearchPage = require('./Search');
class Featured extends Component {
showSearchPage(){
console.log('showing search page');
this.props.navigator.push({
title: "Search",
component: SearchPage
});
}
render() {
return (
<TouchableHighlight onPress={() => this.showSearchPage()}>
<View style={styles.row}>
<Text style={styles.label}>Open Search Page</Text>
</View>
</TouchableHighlight>
);
}
}
module.exports = Featured;
Then have Search.js as another Class very much like the Featured.js . How can i create a button in the view which on tap open Search.js view.
Currently I am getting "Can not read property push of undefined".
How to define navigator or there is other simple way to navigate?
NOTE: I don't want to make TabBar navigation.
So basically you have an app page which render either subviews depending on user actions.
First of all I suggest you use state instead of props for what you are doing (it's dynamic not static).
var SearchPage = require('./Search');
var Initial = require('./Initial');
var AnotherSubView = // ...
class Featured extends Component {
getInitialState: function() {
title: "initial",
component: <Initial/>
return({title:title, component:component});
}
showSearchPage(){
console.log('showing search page');
this.setState({
title: "Search",
component: <SearchPage/>
});
}
render() {
var title = this.state.title;
var component = this.state.component;
return (<div>
<TouchableHighlight onPress={() => this.showSearchPage()}>
<View style={styles.row}>
<Text style={styles.label}>Open Search Page</Text>
</View>
</TouchableHighlight>
{component}
</div>);
}
}
module.exports = Featured;
Use getInitialState to load your first/landing subview. Then change your component on click.
By the way the props was undefined because you didn't define it =p. And you should use setProps. Anyway I think this was not the right was to proceed but so you know.
Hope it helps!

Resources