简体   繁体   中英

In react native this.setState() does not changes the state in fetch function

I have been searching for solutions for day, I learned a lot, but did not figure out what is wrong. Here what I do:

  1. Calling the App constructor; initialising the state loading, dataSource and data
  2. when the component mounts, the program calls the getData function with the requested URL
  3. The getData function is an asynchronous fetch function
  4. then the data is converted to JSON
  5. then the JSON is cloned to became a datablob for the webview
  6. then the setState function is called, changing the loading and the data.

This is where the setState does not fire. Not even the render (it should). Every tutorial, every forum shows it to be this way (and its also logical).

Here is the code:

import Exponent from 'exponent';
import React from 'react';
import {
  StyleSheet,
  Text,
  View,
  ListView,
  ActivityIndicator
} from 'react-native';

import { Button, Card } from 'react-native-material-design';
import {UnitMenuCard} from './unitmenucard.js';

class App extends React.Component {
  constructor(props) {
    super(props);
    const ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});
    this.state = {
      loading: true,
      dataSource: ds,
      data: null
    }
  }

  componentDidMount() {
    //console.log('componentDidMount');
    this.getData('https://dl.dropboxusercontent.com/u/76599014/testDATA.json');
  }

  getData(url) {
    console.log('loading data');
    return fetch(url).then(
      (rawData) => {
        console.log('parsing data');
        //console.table(rawData);
        return rawData.json();
      }
    ).then(
      (jsonData) =>
      {
        console.log('parsing to datablobs');
        let datablobs = this.state.dataSource.cloneWithRows(jsonData);
        console.log('datablobs: ' + datablobs);
        return datablobs;
      }
    ).then(
        (datablobs) => {
          console.log('setting state');
          this.setState = ({
            loading: false,
            data: datablobs
          });
          console.log('the loading is '  + this.state.loading);
          console.log('the data is '  + this.state.data);
        }
    ).catch((errormsg) =>
      {console.error('Loading error: ' + errormsg);}
    );
  }

  render() {
    console.log('loading is ' + this.state.loading);
    var dataToDisplay = '';
    if(this.state.loading) {
      dataToDisplay = <ActivityIndicator animated={true} size='large' />
    } else {
      //let jdt = this.state.dataSource.cloneWithRows(this.state.data);
      dataToDisplay = <ListView
        dataSource={this.state.ds}
        renderRow={(unit) => <UnitMenuCard name={unit.name} image={unit.picture} menu={unit.menu}/>}
        />
    }
    return (
      <View style={styles.container}>
        {dataToDisplay}
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',

  },
});

Exponent.registerRootComponent(App);

Did I missed something? Thank you forward for your answer mighty Stack Overflow.

I think you misunderstood how DatSource works.

getInitialState: function() {
  var ds = new ListViewDataSource({rowHasChanged: this._rowHasChanged});
  return {ds};
},
_onDataArrived(newData) {
  this._data = this._data.concat(newData);
   this.setState({
     ds: this.state.ds.cloneWithRows(this._data)
   });
}

This is taken from the docs here: https://facebook.github.io/react-native/docs/listviewdatasource.html

See how you need to clone your datasource object in the state. The key thing is that you call cloneWithRows passing the data you got back from the API (in your case jsonData ). This creates a cloned datasource, containing the data you just fetched.

In your code instead you just create a clone of your data source, but never replace the actual one you list view is linked to. You should do it this way:

.then(
    (datablobs) => {
      console.log('setting state');
      this.setState = ({
        loading: false,
        dataSource: datablobs
      });
      console.log('the loading is '  + this.state.loading);
      console.log('the data is '  + this.state.data);
    }

You don't need state.data for this, the list view reads from the datasource object. You can also avoid having two .then calls by simply doing everything in one.

You also have another problem. You have the list view linked to the wrong property in the state. You list view code in render should be:

dataToDisplay = <ListView
    dataSource={this.state.dataSource}
    renderRow={(unit) => <UnitMenuCard name={unit.name} image={unit.picture}   menu={unit.menu}/>}
    />

this.state.dataSource is where you store the data source object.

 dataToDisplay = <ListView
        dataSource={this.state.ds}  // <- you set the dataSource to this.state.ds instead of this.state.data
        renderRow={(unit) => <UnitMenuCard name={unit.name} image={unit.picture} menu={unit.menu}/>}
        />

The data source should be the data you got from your fetch call. Therefore, dataSource should be set equal to this.state.data.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM