Daniel Cabrera
Daniel Cabrera

Reputation: 43

ReactNative null is not an object (evaluating 'this.state.dataSource')

I am running the following code in Android emulator but I am getting null is not an object (evaluating 'this.state.dataSource') error. Please, could you help me to see what I am doing wrong? For some reason the line dataSource={this.state.dataSource} is getting null.

import React, {
  Component
} from 'react';
import {
  AppRegistry,
  ActivityIndicator,
  ListView,
  Text,
  View,
  StyleSheet
} from 'react-native';
import Row from './Row';
import Header from './Header';
import SectionHeader from './SectionHeader';

const styles = StyleSheet.create({
  container: {
    flex: 1,
    marginTop: 20,
  },
  separator: {
    flex: 1,
    height: StyleSheet.hairlineWidth,
    backgroundColor: '#8E8E8E',
  },
});

export default class NoTocarList extends Component {
  constructor(props) {
    super(props);
    const getSectionData = (dataBlob, sectionId) => dataBlob[sectionId];
    const getRowData = (dataBlob, sectionId, rowId) =>
      dataBlob[`${rowId}`];

    fetch('http://xxxxx.mybluemix.net/get')
      .then((response) => response.json())
      .then((responseJson) => {
        const ds = new ListView.DataSource({
          rowHasChanged: (r1, r2) => r1 !== r2,
          sectionHeaderHasChanged: (s1, s2) => s1 !== s2,
          getSectionData,
          getRowData
        });

        const {
          dataBlob,
          sectionIds,
          rowIds
        } =
        this.formatData(responseJson);

        this.state = {
          dataSource: ds.cloneWithRowsAndSections(dataBlob, sectionIds,
            rowIds)
        }
      })
  }


  formatData(data) {
    const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('');
    const dataBlob = {};
    const sectionIds = [];
    const rowIds = [];

    for (let sectionId = 0; sectionId < alphabet.length; sectionId++) {
      const currentChar = alphabet[sectionId];
      const users = data.filter((user) =>
        user.calle.toUpperCase().indexOf(currentChar) === 0);

      if (users.length > 0) {
        sectionIds.push(sectionId);

        dataBlob[sectionId] = {
          character: currentChar
        };
        rowIds.push([]);

        for (let i = 0; i < users.length; i++) {
          const rowId = `${sectionId}:${i}`;
          rowIds[rowIds.length - 1].push(rowId);
          dataBlob[rowId] = users[i];
        }
      }
    }
    return {
      dataBlob,
      sectionIds,
      rowIds
    };
  }

  render() {
    return (
      <View style={{flex: 1, paddingTop: 20}}>
        <ListView
          style={styles.container}
          dataSource={this.state.dataSource}
          renderRow={(rowData) => <Row {...rowData} />}
          renderSeparator={(sectionId, rowId) => <View key={rowId} />}
          style={styles.separator}
          renderHeader={() => <Header />}
          renderSectionHeader={(sectionData) => <SectionHeader {...sectionData} />}
        />
      </View>
    );

  }

}


AppRegistry.registerComponent('NoTocar', () => NoTocarList);

Error Message

Upvotes: 2

Views: 10984

Answers (2)

nem035
nem035

Reputation: 35491

The issue is that you're trying to update the state asynchronously, after a render, but are expecting the result on the first render. Another issue is that you're overwriting the state instead of updating it.

The fetch call in your constructor is async, meaning the constructor is finished and the component (along with its state) is created before that call resolves.

constructor() {

  fetch('http://xxxxx.mybluemix.net/get')
  // ...
  .then(() => {
  // ...

    // this code gets called after the component is created
    // this state is also overwriting already created state
    this.state = {
      dataSource: ds.cloneWithRowsAndSections(dataBlob, sectionIds, 
                  rowIds)
    }
  })
}

Since your data is obtained asynchronously, you can add a check and show a progress indicator while its loading (you should also use setState instead of overwriting the state):

constructor() {

  this.state = {
    dataSource: null // initialize it explicitly
  }

  fetch('http://xxxxx.mybluemix.net/get')
  // ...
  .then(() => {
  // ...

    // use set state
    this.setState({
      dataSource: ds.cloneWithRowsAndSections(dataBlob, sectionIds, 
                  rowIds)
    })
  })
}

render(){

  // on initial render, while the state hasn't been fetched yet
  // show the spinner
  if (!this.state.dataSource) {
    return <ActivityIndicator />
  }

  return(
    <View style={{flex: 1, paddingTop: 20}}>
      ...
    </View>
  );
}

Upvotes: 2

Jeffrey
Jeffrey

Reputation: 4650

From your example you have passed the dataSource as null to the ListView. So you need to initialize it first by using

this.state({
  dataSource: {}
})

After getting the response from the Api call you need to set the dataSource state by using

this.setState({
  dataSource: ds.cloneWithRowsAndSections(dataBlob, sectionIds, 
                  rowIds)
})

Upvotes: 2

Related Questions