React Component only updates once - javascript

Consider the following React Native code:
import React from 'react';
import {Text, View, StyleSheet, Button} from 'react-native';
export default class Target extends React.Component {
constructor(props){
super(props);
this.state ={ isLoading: true};
this.hitData = this.props.hitData;
}
componentDidMount(){
for(let i of Object.keys(this.hitData)){
if(this.hitData[i] === 0){
this.hitData[i] = '---'
}
}
this.setState({
prop1: this.hitData['prop1'],
prop2: this.hitData['prop2'],
prop3: this.hitData['prop3'],
prop4: this.hitData['prop4'],
prop5: this.hitData['prop5'],
isLoading: false
});
this.onPress = this.onPress.bind(this);
}
componentDidUpdate(prevProps) {
// console.log(prevProps)
if(this.props.hitData !== prevProps.hitData){
console.log("Component "+ this.state.mac + " got new props!");
this.hitData = this.props.hitData;
this.setState((state, props) =>{
let newState = {};
for(let i of Object.keys(props.hitData))
{
if(state[i] !== props.hitData[i])
{
console.log(i + " is different!");
newState[i] = props.hitData[i]
}
}
return newState
});
}
}
onPress(txt) {
console.log(txt);
}
render(){
return(
<View style={styles.container}>
<View style={{flex: 1, flexDirection: 'row'}}>
<View style={{borderWidth: 2.5, borderColor: '#00FF00',width: '50%'}}>
<Text style={{fontSize: 19, fontWeight: 'bold'}}>{this.state.prop1}</Text>
<Text style={{fontSize: 16}} numberOfLines={1}>{this.state.prop2}</Text>
</View>
<View>
<Text>{"Prop3: " + this.state.prop3}</Text>
<Text>{"Prop4: " + this.state.prop4}</Text>
<Text>{"Prop5: " + this.state.prop4}</Text>
</View>
</View>
<Button key={this.hitData[0]} title={"BUTTON"} onPress={() => this.onPress(this.hitData[0])}/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
borderRadius: 4,
borderWidth: 2.5,
height: 100,
marginBottom: 5,
overflow: 'hidden'
}
});
This is the code for a React Native component. The component takes in information received from an EventSource in the parent element. The aim is to update the fields containing Prop[3-5]based on data from future events.
In the parent component I have a property per item on state and am defining each element like:
const targetList = this.state.filterKeys.map((item) =>
<Target key={item} hitData={this.state[item]['lastHit']}/>
In my Event handler I can then do:
this.setState({
[id]: hitIntermediate
})
to send new props to each child component.
In my child component I do see the new props arriving, but after the first update the child stops updating. It does not matter how many events I send, after the first update has been received no further updates are displayed.
The strange part is if I query this.state in the child component, I do see that the state reflects the data from the new event, but the display does not change.
Am I completely misunderstanding something here. Essentially what I am wanting to do is to do the equivalent of setting .innerHTML on the <Text> tags containing the various pieces of data, but all my updates seem to get ignored after the first.

I was able to resolve this on my own.
As it turns out, the way I was getting events into my component was silly.
I changed it so instead of maintaining a hugely complex state out in the main component, I how just pass in the EventSource and define an eventhandler inside each child component.
Now I am seeing updates in close to real-time. I would still like to improve re-render performance a little bit, but overall the issue I was having is resolved.
Now a quick bonus question: why is it I seem to see (slightly) faster performance if I have the react-devtools running?

Related

Trouble with React Native and Firebase RTDB

I'm using React Native (0.68) and Firebase RTDB (with the SDK, version 9), in Expo.
I have a screen that needs to pull a bunch of data from the RTDB and display it in a Flatlist.
(I initially did this without Flatlist, but initial rendering was a bit on the slow side.)
With Flatlist, initial rendering is super fast, huzzah!
However, I have an infinite loop re-render that I'm having trouble finding and fixing. Here's my code for the screen, which exists within a stack navigator:
export function GroupingsScreen () {
... set up a whole bunch of useState, database references (incl groupsRef) etc ...
onValue(groupsRef, (snapshot) => {
console.log('groups onValue triggered')
let data = snapshot.val();
if (loaded == false) {
console.log('--start processing')
setLoaded(true);
let newObject = []
for (let [thisgrouping, contents] of Object.entries(data)) {
let onegroupingObject = { title: thisgrouping, data: [] }
for (let [name, innerdata] of Object.entries(contents.ingredients)) {
onegroupingObject.data.push({ name: name, sku: innerdata.sku, size: innerdata.size,
quantity: innerdata.quantity,
parent: thisgrouping
})
}
newObject.push(onegroupingObject)
}
console.log('--done processing')
setGroupsArray(newObject)
}
});
.... more stuff excerpted ....
return (
<View style={styles.tile}>
<SectionList
sections={groupsArray}
getItemLayout={getItemLayout}
renderItem={ oneRender }
renderSectionHeader={oneSection}
initialNumToRender={20}
removeClippedSubviews={true}
/>
</View>
)};
I'm using loaded/setLoaded to reduce re-renders, but without that code, RN immediately dumps me out for excessive re-renders. Even with it, I get lots of extra renders.
So...
Can someone point me at what's triggering the rerender? The database is /not/ changing.
Is there a better way to get RTDB info into a Flatlist than the code I've written?
I have some code that actually does change the database. That's triggering a full rerender of the whole Flatlist, which is visibly, painfully slow (probably because parts are actually rendering 10x instead of once). Help?
For completeness, here's the OneItem code, so you can see just how complex my Flatlist items are:
const OneItem = (data) => {
// console.log('got data',data)
return (
<View style={[styles.rowView, { backgroundColor: data.sku?'white': '#cccccc'}]} key={data.name}>
<TouchableOpacity style={styles.nameView} onPress={() => {
navigation.navigate('AddEditItemScreen', {purpose: 'Grouping', itemname: data.name, parent: data.parent, mode: 'fix'})
}}>
<View style={styles.nameView}>
<Text style={styles.itemtext}>{data.name}</Text>
{data.sku? null: <Text>"Tap to add SKU."</Text>}
{data.size?<Text>{data.size} </Text>: <Text>no size</Text>}
</View>
</TouchableOpacity>
<View style={styles.buttonView}>
<Button style={styles.smallButton}
onPress={() => { changeQuant(data.quantity ? data.quantity - 1 : -1, data.parent + '/ingredients/' + data.name) }}
>
{data.quantity > 0 ? <Text style={[styles.buttonText, { fontSize: 20 }]}>-</Text>
:<Image source={Images.trash} style={styles.trashButton} />}</Button>
<Text style={styles.quantitytext}>{data.quantity}</Text>
<Button style={styles.smallButton}
onPress={() => {
changeQuant(data.quantity? data.quantity +1 : 1, data.parent+'/ingredients/'+data.name)}}>
<Text style={[styles.buttonText, {fontSize: 20}]}>+</Text></Button>
</View>
</View>
)
};```
I worked out how to stop the rerender (question #1). So, within my Screen functional component, I needed to make another function, and attach the state hook and useEffect to that. I'm not totally sure I understand why, but it gets rid of extra renders. And it's enough to get #3 to tolerable, although perhaps not perfect.
Here's the new code:
export function GroupingsScreen () {
... lots of stuff omitted ...
function JustTheList() {
const [groupsArray, setGroupsArray] = useState([])
useEffect(() => {
const subscriber = onValue(groupsRef, (snapshot) => {
console.log('groups onValue triggered')
let data = snapshot.val();
let newObject = []
for (let [thisgrouping, contents] of Object.entries(data)) {
let onegroupingObject = { title: thisgrouping, data: [] }
for (let [name, innerdata] of Object.entries(contents.ingredients)) {
onegroupingObject.data.push({ name: name, sku: innerdata.sku, size: innerdata.size,
quantity: innerdata.quantity,
parent: thisgrouping
})
}
newObject.push(onegroupingObject)
}
setGroupsArray(newObject)
})
return () => subscriber();
}, [])
return(
<View style={styles.tile}>
<SectionList
sections={groupsArray}
getItemLayout={getItemLayout}
renderItem={ oneRender }
renderSectionHeader={oneSection}
initialNumToRender={20}
removeClippedSubviews={true}
/>
</View>
)
}
And then what was my return within the main functional screen component became:
return (
<JustTheList />
)
I'm still very interested in ideas for improving this code - am I missing a better way to work with RTDB and Flatlist?

When to switch from storing nested component variables in this.props vs this.state

I'm getting back into React and trying to replicate an instagram post. Variables that don't change such as the name of the poster, location, etc I think I have a solid understanding of how to handle. I keep in them in the chain of this.props as they will be immutable from the user perspective. But when it comes to something like "reactions", I am unsure of when to begin storing them in this.state vs. this.props due to the multiple levels of nesting.
Should I keep the data relating to my reactions array / object in this.state (as I attempt to do below) for all nested components all the way down? Or keep them in this.props until I get to the lowest child component?
I don't have any code for lifting state up since I want to make sure my architecture is correct first.
Home.js
const POST_DATA = [
{
name: 'Outdoors Guy',
location: 'Yosemite, CA',
id: '123',
body: 'Hello world!',
image: 'https://someurl',
reactions: [
{
reaction: 'Like',
count: 2,
selected: true
},
{
reaction: 'Love',
count: 1,
selected: false
}
]
}
];
export default class HomeScreen extends React.Component {
constructor(props){
super(props)
this.state = {
posts: POST_DATA
}
}
render(){
return (
<View style={styles.container}>
<StatusBar barStyle="dark-content" />
<ScrollView>
<FlatList styles={styles.list}
data={this.state.posts}
renderItem={({ item }) =>
<Post
name={item.name}
location={item.location}
body={item.body}
image={item.image}
reactions={item.reactions}/>}
keyExtractor={item => item.id}/>
</ScrollView>
</View>
);
}
}
Post.js
export default class Post extends Component {
constructor(props){
super(props)
state = {
// should I be doing this?
reactions: this.props.reactions
}
}
render() {
return (
<View>
<View style={styles.postHeader}>
// Example of these two variables ending their chaining here
<Text style={styles.name}>{this.props.name}</Text>
<Text style={styles.location}>{this.props.location}</Text>
</View>
<Text style={styles.body}>{this.props.body}</Text>
<Image style={styles.image} source={{uri: this.props.image}}/>
// Heres where the confusion begins
<ReactionList list={this.props.reactions}/>
</View>
);
}
}
ReactionList.js
export default class ReactionList extends Component {
constructor(props){
super(props)
state = {
reactions: this.props.reactions
}
}
render() {
if (this.state.reactions === null)
{
return (
<View>
<AddReaction/>
<FlatList styles={styles.reactionList}
data={this.state.reactions}
renderItem={({ item }) =>
<Reaction
reaction={item.reaction}
count={item.count}
selected={item.selected}/>}
keyExtractor={item => item.id}/>
</View>
)
}
else{
return (
<View>
<AddReaction/>
</View>
)
}
}
}
Reaction.js
export default class Reaction extends Component {
constructor(props){
super(props)
state = {
// I understand these are not visible in the component
// but still want to track the data
count: this.props.count,
selected: this.props.selected
}
}
_onSelectPress () {
this.setState({
selected: !this.state.selected,
count: ++this.state.count
})
}
render() {
return (
<TouchableOpacity style={styles.reaction}
onPress={() => this._onSelectPress()}>
<Text style={styles.reactionText}>{this.props.reaction}</Text>
</TouchableOpacity>
);
}
If it makes sense to you, you could store the POST_DATA completely inside the state, as this will greatly simplify how the Parent component who holds the post information and the Child Post component behave.
For example, what if, instead of just letting the user edit reactions, you wanted the post owner to be able to edit the body or location of POST_DATA? Or what if you wanted the reactions count to be accesible to say, a separate modal (when you click on the post)?
A way to future-proof this design, while keeping it simple, is to send the whole POST_DATA to your <Post> component and if you don't want to use a library such as Redux to handle the state, then lift the callback for edits up:
Here's a simplified Codesandbox I did of this implementation.
<InstaPost
post={this.state.post} <-- this.state.post holds POST_DATA
handleReactionClick={this.handleReactionClick}
handleBodyEdit={this.handleBodyEdit} <-- if we decide to implement this later on
/>
Why hold network data as an Immutable object in the app/component state?
The benefits of holding the post data in the state, as an Immutable object (in the example I used immer.js), is that if, for example, the way Instagram or Facebook work is that, when you click on the "Love" reaction, an API request is sent for your reaction, and it returns the value of the reactions updated real-time:
So for example, if I pressed on "Like (2)", and 10 more people pressed like before that, the API wouldn't return "3" (2+1), but rather, "13" (2 + 1 + 10). Handling this with your current design would require much more work than with the design I propose.
Personally, I would prefer using a library such as Redux for application state, but for a small prototype it's not necessary. I do, however, recommend always using immutable objects whatever the size of the project if network fetching/updating is involved.
Hope this helps!

Sending custom props to custom component is failing

I have created a custom component which takes a color name from the parent component and updates that color in the state in the parent component. Currently, after I have done all the code, it does not save the new color, and therefore, does not update the the state.
This is for a react-native android app that I am building. I have looked at the ReactNative documentation for flatlist and textinput. I have looked at Stack overflow for solutions too
Set up a react native project. this is my parent component
class HomePage extends Component {
constructor(props) {
super(props);
this.state = {
backgroundColor: "blue",
availableColors: [
{name: 'red'}
]
}
this.changeColor = this.changeColor.bind(this)
this.newColor = this.newColor.bind(this)
}
changeColor(backgroundColor){
this.setState({
backgroundColor,
})
}
newColor(color){
const availableColors = [
...this.state.availableColors,
color
]
this.setState({
availableColors
})
}
renderHeader = ()=>{
return(
<ColorForm onNewColor={this.newColor} />
)
}
render() {
const { container, row, sample, text, button } = style
const { backgroundColor, availableColors } = this.state
return (
<View style={[container,{backgroundColor}, {flex: 1}]} >
<FlatList
data={availableColors}
renderItem={
({item}) =>
<ColorButton
backgroundColor={item.name}
onSelect={(color)=>{this.changeColor(color)}}>
{item.name}
</ColorButton>}
keyExtractor={(item, index) => index.toString()}
ListHeaderComponent={this.renderHeader}
>
</FlatList>
</View>
);
}
}
this is the code for ColorForm component
class ColorForm extends Component {
constructor(props) {
super(props);
this.state = {
txtColor:'',
}
this.submit = this.submit.bind(this)
}
submit() {
this.props.onNewColor(this.state.txtColor.toLowerCase())
this.setState({
txtColor: 'yellow',
})
}
render() {
const {container, txtInput, button} = style
return (
<View style={container}>
<TextInput style={txtInput}
placeholder="Enter a color"
onChangeText={(txtColor)=>this.setState({txtColor})}
value={this.state.txtColor}></TextInput>
<Text
style={button}
onPress={this.submit}>Add</Text>
</View> );
}
}
and below is the code for ColorButton component
export default ({backgroundColor, onSelect=f=>f}) => {
const {button, row, sample, text} = style
return (
<TouchableHighlight onPress={()=>{onSelect(backgroundColor)}} underlayColor="orange" style={button}>
<View style={row}>
<View style={[sample,{backgroundColor}]}></View>
<Text style={text}>{backgroundColor}</Text>
</View>
</TouchableHighlight>
)
}
The imports and stylesheets are setup as standard and do not effect the code so I have chosen to not show them.
EDIT: Adding the expo snack here.
Expected Behavior:
When I press "ADD" on the ColorForm component, it should take that color and add that to the this.state.availableColor array and therefore visible in the ColorButton component. And when I touch the button, it should make that change
Current behaviour:
When I enter a color and press on add, it makes an empty button in the ColorButton component - NOT the color i entered in the color I entered in the ColorForm component.
EDIT: Adding the expo snack here.
Your state is updating but the FlatList is not updating. Because your data={availableColors} in flatlist is not changing but your state is changing .
Try to add extraData
A marker property for telling the list to re-render (since it implements PureComponent). If any of your renderItem, Header, Footer, etc. functions depend on anything outside of the data prop, stick it here and treat it immutably.
Try this
<FlatList
extraData={this.state.backgroundColor}
Updated Answer
the problem is in this function newColor(color)
const availableColors = [
...this.state.availableColors,
color
]
you just receive a string of color but you have defined object like this {name: 'red'}
please use this code
newColor(color){
const availableColors = [
...this.state.availableColors,
{name: color}
]
this.setState({
availableColors
})
}
Snack link with example : https://snack.expo.io/#mehran.khan/healthy-cake-state-sample
Also add export default to main component to remove error of launch
export default class HomePage extends Component {
App Preview
I had many problems using setState() in the way you are using now. I recommend you to use in this way setState(), with a callback:
this.setState((previousState, currentProps) => {
return { ...previousState, foo: currentProps.bar };
});
This is one of the article that talks about it.
setState() does not always immediately update the component. It may
batch or defer the update until later.
From react website setState().

When SectionList/Flatlist is scrolling/rendering items UI thread seems blocked (React Native)

We use Sectionlist and Flatlist in our react native project and we use websocket to receive/update data.
Every time we receive data via websocket we want to update some information in our Sectionlist/ Flatlist, but we have more than 100 row so when the list trying to re-render UI thread seems blocked. Therefore, the onPress function delay for 3~5 seconds.
So my questions are:
Does Flastlist/Sectionlist re-render the whole list when it's updating or it only re-render specific rows? (Because we only update some information in some specific rows not every rows have new information to update)
If it re-render the whole list, how I can let Flastlist or Sectionlist only re-render those specific rows?
If it only re-render specific rows, why the UI still seem blocked? How can I prevent this situation?
Or maybe the problem is not about the UI block? Maybe there are some other reasons why the onPress function delay? If so, what are the problems and how can I fix that?
class RowItem1 extends React.Component{
constructor(props){
super(props);
}
shouldComponentUpdate = (nextProps, nextState) => {
if (nextProps.item == this.props.item) {
return false;
}
return true;
};
render=()=>{
return (
<View>
<TouchableWithoutFeedback
onPress={() => { consle.log(1234) }}
>
</TouchableWithoutFeedback>
<View>
<Text>{this.props.item.text}</Text>
<Text>{this.props.item.name}</Text>
</View>
</View>
)
}
}
export default class main extends React.Component {
constructor(props) {
super(props);
}
componentWillMount = () => {
var ws = new WebSocket('ws://123456');
ws.onmessage = function (evt) {
let data = JSON.parse(evt.data);
this.setState({
A: data.A,
B: data.B,
C: data.C
});
};
};
getItemLayout = (data, index) => ({ length: 74, offset: 74 * index, index });
renderA = ({ item, index, section }) => {
return <RowItem1 item={item}/>
}
render() {
return (
<View>
<SectionList
sections={[
{
title: "A",
data: this.state.A,
renderItem: this.renderA,
},
{
title: "B",
data: this.state.B,
renderItem: this.renderB,
},
{
title: "C",
data: this.state.C,
renderItem: this.renderC,
}
]}
initialNumToRender={10}
removeClippedSubviews={true}
getItemLayout={this.getItemLayout}
keyExtractor={(item, index) => item + index}
extraData={this.state}
refreshing={this.state.refreshing}
onRefresh={this.handleRefresh}
/>
</View>
);
}
}
The First time:
data = {A:[{'text': 0, 'name': 'A'},{'text': 0, 'name': 'B'}, {'text': 0, 'name':'C'}]}
The Second time:
data = {A:[{'text': 3, 'name': 'A'},{'text': 0, 'name': 'B'}, {'text': 0, 'name':'C'}]}
Compare the two data I received, I only want to re-render the first row of the list because there is only the first row of data got updated. How can I do that?
I know this is really late, but for those of you still having this issue, you can fix it by setting the scrollEventThrottle property to something really high, like 1000. I should note that this will really only work if you don't need the onScroll event.
<FlatList data={data} renderItem={renderItem} scrollEventThrottle={1000} />

Event Handler (prop) passed to child component cannot be called react native

I am passing an event handler showSpinner() from parent component. This method displays the activity Indicator in my app, the method when called from the parent class, works. But when I pass it down to a child component and then call it from the child as this.props.showSpinner(), I am getting the error
TypeError: undefined is not an object
(evaluating 'Object.keys(this.state.data)')
I am also not able to console.log the method at the child's props
Please note, I have already bound the function at the parent.
Here is a part of my code.
This is the parent component.
import React from 'react';
import { View, Button, Alert, Image, ScrollView, BackHandler, TouchableOpacity,Text, ActivityIndicator } from 'react-native';
import ProductListingItem from '../ProductCategories/ProductListingItemCategories.js';
import PusherColumnCategories from '../ProductCategories/PusherColumnCategories.js';
import NavRightButton from '../NavButton/NavRightButton.js';
import ActivitySpinner from '../ActivitySpinner.js';
const TAG = 'PRODUCTCATEGORIESPAGE';
export default class ProductCategoriesPage extends React.Component {
constructor(props) {
super(props);
/*this._getResponseFromApi = this._getResponseFromApi.bind(this);*/
this._onPressGoToCart=this._onPressGoToCart.bind(this);
if(props){
/*console.log(TAG,'constructor()','props available');*/
console.log(TAG,'constructor()','props JSON stringified = '+JSON.stringify(props));
/*this.setState({dataMain : (props.navigation.state.params.categories)});*/
}
this.state = {
dataMain: props.navigation.state.params.categories,
showIndicator: false,
};
console.log(TAG,'constructor','this.state.dataMain = '+this.state.dataMain );
}
static navigationOptions = ({navigation}) => {
return{
title: 'Categories',
headerLeft: null,
headerStyle: {
backgroundColor: '#EE162C',
},
/*headerBackTitleStyle: {
color: 'white',
},*/
headerTintColor: 'white',
headerRight: <NavRightButton navigation= {navigation}/>,
gesturesEnabled:false,
};
};
_onPressGoToCart(){
console.log(TAG,'_onPressGoToCart');
console.log(TAG,'_onPressGoToCart','navigation props ='+JSON.stringify(this.props));
const { navigate } = this.props.navigation;
navigate('CartPage');
}
componentWillReceiveProps(newProps){
console.log(TAG+'componentWillReceiveProps');
if(newProps){
console.log(TAG,'componentWillReceiveProps()','props available');
console.log(TAG,'componentWillReceiveProps()','props = '+newProps.navigation.state.params.categories);
}
}
_OnAlert(title,message){
console.log(TAG,'_onAlert');
Alert.alert(
title,
message,
[
{text:'done',onPress: () => { }}
]
);
}
componentDidMount () {
console.log(TAG,'componentDidMount');
/*this._getResponseFromApi();*/
BackHandler.addEventListener('hardwareBackPress',() => {return true});
}
componentWillMount () {
console.log(TAG,'componentWillMount');
}
_showSpinner(){
console.log(TAG,'_showSpinner');
this.setState({
showIndicator:true,
});
}
_hideSpinner(){
console.log(TAG,'_hideSpinner');
this.setState({
showIndicator:false,
});
}
render(){
console.log(TAG,'render');
console.log(TAG,'render','dataMain = '+this.state.dataMain[0].id);
// console.log(TAG,'render','showSpinner = '+JSON.stringify(this.showSpinner()));
// var tempshowspinner = this.showSpinner.bind(this);
// console.log(TAG,'render','tempshowspinner = '+JSON.stringify(tempshowspinner));
return(
<View
style={{
flex:1,
}}>
<ScrollView style = {{flex:1,
backgroundColor:'#F2F2F2',
}}>
<View style = {{
flex:1,
flexDirection:'column',
}}>
<PusherColumnCategories style = {{
flex:1,
}}
data = {this.state.dataMain}
navigate = {this.props.navigation}
showSpinner = {this._showSpinner}
hideSpinner = {this._hideSpinner}/>
</View>
</ScrollView>
<ActivitySpinner showIndicator={this.state.showIndicator}/>
</View>
);
}
}
This is the corresponding child component.
import React from 'react';
import {View, Component, Button} from 'react-native';
import ProductListingItem from './ProductListingItemCategories';
const TAG = "PUSHERCOLUMNCATEGORIES";
export default class PusherColumnCategories extends React.Component {
constructor(props){
super(props);
if(props){
console.log(TAG,'props ='+JSON.stringify(props));
/*console.log(TAG,'props data length = '+Object.keys(props.dataMain).length);*/
console.log(TAG,'Props = '+ JSON.stringify(props.data));
console.log(TAG,'Navigation Props = '+JSON.stringify(props.navigate));
}
this.state = {
data: props.data,
propsAvailable: false,
navigate: props.navigation,
};
};
componentDidMount(){
console.log(TAG,'componentDidMount');
}
componentWillReceiveProps(newProps){
console.log(TAG,'componentWillReceiveProps',newProps.data);
this.setState({
/*data: JSON.parse(JSON.stringify(newProps.data)),*/
data: (newProps.dataMain),
}, function() {
console.log(TAG,'componentWillReceiveProps','this.setState()','data = '+(Object.keys(this.state.data)));
});
}
componentDidUpdate(){
console.log(TAG,'componentDidUpdate');
}
render(){
console.log(TAG,'render()');
if(this.state.data){
console.log(TAG,'render()','state not empty');
console.log(TAG,'render()','data product_code = '+this.state.data[1].product_code);
return(
<View style = {{
flex:1,
flexDirection: 'column',
}}>
<Button
style = {{
flex:1,
}}
title = 'presshere'
onClick = {this.props.showSpinner}
/>
<RenderColumn style = {{
flex:1,
}}
data = {this.state.data}
navigate = {this.props.navigate}
showSpinner = {this.props.showSpinner}
hideSpinner = {this.props.hideSpinner}/>
</View>
);
} else {
console.log(TAG,'render()','state empty');
return(
<View style = {{
flex:1,
flexDirection: 'column',
}}/>
);
}
};
}
EDIT: I actually found the real problem. The binding stuff is good to know, but it's not the answer to the actual question. The problem is newProps.dataMain. newProps does not have a dataMain key on it, it's actually data as you can see below
<PusherColumnCategories style = {{
flex:1,
}}
data = {this.state.dataMain} // the key is `data`, not `dataMain`
navigate = {this.props.navigation}
showSpinner = {this._showSpinner}
hideSpinner = {this._hideSpinner}/>
So in this piece of code in componentWillReceiveProps
this.setState({
/*data: JSON.parse(JSON.stringify(newProps.data)),*/
data: newProps.data /* not newProps.dataMain */, // newProps.dataMain is not an actual key, so it will set `data` to undefined
}, function() {
console.log(TAG,'componentWillReceiveProps','this.setState()','data = '+(Object.keys(this.state.data))); // if you call `Object.keys` on undefined, you get the error that you posted
});
When you do myFunction.bind(obj), you create a new function that wraps your existing function and remembers the object obj you pass in, and whenever you call that new function, it calls your original function with this set to obj.
In your _showSpinner an _hideSpinner functions, you make use of this.setState, so it's important that this is set to your parent component so that you update the state of parent component, and you're trying to make sure of that, but you're also binding unnecessarily at a lot of places, like in the constructor of ProductCategoriesPage, which is unnecessary. And you also want to remove bind(this) from the following
tempshowspinner in the render function of PusherColumnCategories. this.props.showSpinner already updates the parent component since it's bound to it. So it's redundant binding it here again, which gives the wrong impression that you're binding it again to the child component, which is not the case.
The bind(this) in the Button component right below. You don't want to bind over here for the same reason

Categories