Appended React Native component not rerendering - reactjs

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.

Related

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 click highlighted word

I'm using react-native-highlight-words to highlight hashtagged words in my react-native app. It highlights required words properly but I want to make it clickable too which is not provided by this library. Means when I will click #positivewibes word, it redirect me to another page.
I've uploaded the image for reference here.
My Code
import Highlighter from 'react-native-highlight-words';
export default class LikeComponent extends Component {
constructor(props) {
super(props);
this.state = {
highlightWordArray: []
};
}
componentDidMount() {
postText = this.props.postData.details;
var regexp = new RegExp('#([^\\s]*)','g');
postText = postText.match(regexp);
if(postText != null) {
this.setState({highlightWordArray: postText});
}
}
render() {
return (
<Highlighter
highlightStyle={{color: 'red'}}
searchWords={this.state.highlightWordArray}
textToHighlight= {this.props.postData.details}
/>
)}
}
Any help is appreciated. Thank you.
You can fork and modify the library code by providing an additional prop - onPress in the file as
<Text
onPress={props.onPress}
key={index}
style={chunk.highlight && highlightStyle}
>
{text}
</Text>
and later use it as
<Highlighter
...// other props
onPress={// your redirect instance}
/>

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>
)
})
}
}

React Native Make State Change From different component

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.

What causes an "React.Children.only expected to receive a single React element child." error in an apollo client rendered component

Why does adding another 2nd element to a view cause the error "React.Children.only expected to receive a single React element child."
I have a react native component that is using apollo client to call a graphQL endpoint to get data. If I have it render just a the view, it works, however when I add the touchable highlight I get the error. Can I not have more than one root element in a view, or make it a view with composite elements? I tried adding it as a Button which also threw a different error. Adding a text seems ok however.
class Games extends Component {
constructor(props) {
super(props);
}
render() {
const { loading, allGames } = this.props.data;
if (loading) {
return <Text>Loading</Text>;
} else if (allGames) {
return (
<View style={styles.outer}>
//adding this touchable highlight causes the error
<TouchableHighlight>Get More</TouchableHighlight>
{ allGames.map(game => (
<View key={game.id} style={styles.wrapper}>
<Text style={styles.header}>{game.title}</Text>
</View>
))}
</View>
);
}
return (
<Text>No results</Text>
);
}
}
Games.propTypes = {
data: PropTypes.shape({
loading: PropTypes.bool.isRequired,
allGames: PropTypes.array,
}).isRequired,
};
According to https://facebook.github.io/react-native/docs/touchablehighlight.html,
TouchableHighlight must have one child (not zero or more than one). You can try to add a child element.
<TouchableHighlight>
<Text>Get More</Text>
</TouchableHighlight>

Resources