Andrea Di Lisio Andrea Di Lisio - 2 months ago 18
React JSX Question

React Native: this.state of reusable components not expected

I'm a newbie of React native and honestly I have just a very basic knowledge of React. I'm developing a sample application in which I make use of reusable components and ES6 sintax.

I'am experiencing unexpected results when reusing the same component multiple times in the same Scene (I also make use of Navigator). More precisely I can't understand why differents components (of the same type) are apparently conditions each others states.

I'm posting my code for a better understanding.

This is my main page , in which I make use 2 times of the same custom defined component < TopCategories /> :

HomeScene.js

import React from 'react';
import {View} from 'react-native';

import BaseScene from './BaseScene'
import SearchSuggest from '../components/SearchSuggest';
import TopCategories from '../components/TopCategories'

import styles from '../styles'


export default class HomeScene extends BaseScene {
render() {
return(
<View style={styles.container}>
<SearchSuggest
navigator={this.props.navigator}
/>

<TopCategories/> //first

<TopCategories/> //second

</View>
)
}
}


These are the details of the inner components used:

TopCategories.js

import React, { Component } from 'react';
import {
Text,
View,
StyleSheet
} from 'react-native';

import styles from '../styles'
import utility from '../utility'
import serverConnector from '../serverConnector'
import routes from '../routes'

import MenuItemComplex from './MenuItemComplex'

export default class TopCategories extends Component {

constructor(props) {
super(props);
this.state = {categories: []};
this._fetchContent();
}


_updateCategoriesList(rawCategories){
//store in state.categories array a simplied version of
//the contents received from the server

let simplifiedCategories = [];
for(i=0; i<rawCategories.length; i++){
var simpleCat = {};
simpleCat.id = rawCategories[i].uniqueID;
simpleCat.name = rawCategories[i].name;
simplifiedCategories.push(simpleCat);
}

this.setState({categories: simplifiedCategories });
}

_fetchContent(){
//fetch content from server in JSON format
_topCategories = this;
serverConnector.call(
"CATEGORY",
"FindTopCategories",
{},
function(err, json){
if(err!=null) utility.log("e", err);
else {
try{
_topCategories._updateCategoriesList(json.res.header.body.CatalogGroupView);
}catch(err){
utility.log("e", err);
}
}
}
)
}

openCategoryScene(id, name){
//push on Navigator stack the next route with additional data
let nextRoute = routes.get("categoriesListFirst");
nextRoute.passProps = {
categoryId: id,
categoryName: name
};
this.props.navigate(nextRoute)
}

render(){
console.log(this.state)
return (
<MenuItemComplex key="categories" name="Catalogo" icon="list-b" onItemSelected={this.openCategoryScene.bind(this)} subItems={this.state.categories} />
)
}
}


and finally
MenuItemComplex.js

import React, { Component } from 'react';
import { View, Text, Image, TouchableHighlight, TouchableWithoutFeedback } from 'react-native';

import styles from '../styles'

export default class MenuItemComplex extends Component{

static propTypes = {
name : React.PropTypes.string.isRequired,
icon : React.PropTypes.string.isRequired,
subItems: React.PropTypes.array.isRequired,
onItemSelected: React.PropTypes.func.isRequired
};

render(){
let subItems = [];
for(i=0; i<this.props.subItems.length; i++){
let subItem = this.props.subItems[i];
subItems.push(
<TouchableHighlight
key={subItem.id}
underlayColor={"#d00"}
activeOpacity={1}
onPress={() => this.props.onItemSelected(subItem.id, subItem.name)}
>
<View style={styles.menuSubItem}>
<Text style={[styles.mmText, styles.menuSubItemText]} >
{subItem.name}
</Text>
</View>
</TouchableHighlight>
)
}

return(

<View>
<TouchableWithoutFeedback disabled={true}>
<View style={styles.menuItem}>
<Image style={styles.menuItemImage} source={{uri: this.props.icon}} />
<Text style={[styles.mmTextBold, styles.menuItemText]}>{this.props.name}</Text>
</View>
</TouchableWithoutFeedback>

{subItems}

</View>
)
}
}


I can't understand why the state.simplifiedCategories of the first < TopCategories > component used in my HomeScene seems to be an empty array after the second < TopCategories > component has rendered. So far I thought that the two components were completely isolated, with their own "private" state. But in this case seems that this is shared somehow.

Can someone explain what is happening here ? And then how can I fix that ?

Thanks

EDIT 2016/09/05
As suggested by user V-SHY I tried to give every component a randomic string as key, but this does not solve the problem.
What I find very strange is that I can see only an instance of < TopCategories > in the global window object, the last one.

The screenshot here refers to a test made with

<TopCategories key="tc_first" {...this.props}/>

<TopCategories key="tc_second" {...this.props}/>


in the HomeScene.js file

console.log(window) result

Answer

As suggested by Daniel there was an issue during the fetch of data from server. In particular I was wrong when creating a _topCategories=this object inside TopCategories.js file

_fetchContent(){
    _topCategories = this; // ISSUE HERE!
    serverConnector.call(
      "CATEGORY",
      "FindTopCategories",
      {},
      function(err, json){
        if(err!=null) utility.log("e", err);
        else {
          try{
            _topCategories._updateCategoriesList(json.res.header.body.CatalogGroupView);
          }catch(err){
            utility.log("e", err);
          }
        }
      }
    )
  }

I solved passing the fetchContent method a reference of the component :

constructor(props) {
    super(props);
    this.state = {categories: []};
    this._fetchContent(this); // passing THIS reference
  }


  _updateCategoriesList(rawCategories){
    let simplifiedCategories = [];
    for(i=0; i<rawCategories.length; i++){
      var simpleCat = {};
      simpleCat.id = rawCategories[i].uniqueID;
      simpleCat.name = rawCategories[i].name;
      simplifiedCategories.push(simpleCat);
    }

    this.setState({categories: simplifiedCategories});
  }

  _fetchContent(instanceRef){
    motifConnector.call(
      "CATEGORY",
      "FindTopCategories",
      {},
      function(err, json){
        if(err!=null) utility.log("e", err);
        else {
          try{
            instanceRef._updateCategoriesList(json.res.header.body.CatalogGroupView);
          }catch(err){
            utility.log("e", err);
          }
        }
      }
    )
  }