Monday, September 25, 2017

A simple list view implementation in React native

As I started on my React Native lessons, it was very difficult to stop by one place for all information. Especially to display a listview, load data from a remote service also format listview for a better presentation

Here I have a simple Listview that shows how to get started

  1. Create a Listview component
  2. Load data from a remote JSON service
  3. Format rows for data
  4. Setup a header (you can set up a footer in a similar fashion)
  5. Do some basic styles
  6. Show error, if remote service fails
A basic functional ListView can be created from here. What we are about to do is create a ListView that can do a service call. Implementation for this service is done in separate component. So is the error. With that said, people with mobile development background will know how complicated ListRows and TableRows can get. It's a better idea for us to separate header, footer and rows into their own components too. Our ListView component now has the following other component that works with it

  1. A Vanilla Listview
  2. Custom Header
  3. Custom Row
  4. Custom service component (Acts as a base class)
  5. Finally, a separate error component
This code can be done better in many ways. But for us to get started, this will be easy to follow and change if needed

index.ios.js/index.android.js


/**
 * Sample React Native App
 * https://github.com/facebook/react-native
 * @flow
 */

import React, { Component } from 'react';
import {
  AppRegistry,
  StyleSheet,
  Text,
  ListView,
  View
} from 'react-native';
import ListHeader from './src/components/list_header';
import ListRow from './src/components/list_row';
import PageError from './src/components/page_error'
import CountriesService from './src/services/countries';

export default class SimpleListView extends CountriesService {

  constructor() {
    super();
    ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});
    var currentState = this.state;
    this.state = {
      countries: currentState,
      dataSource: ds.cloneWithRows(['row 1', 'row 2']),
      error: '',
      rowHeaderText: 'Countries and their codes',
    };
    console.dir(this.state);
  }

  componentDidMount() {
    this.loadJSONData();
  }

  loadJSONData() {
    fetch(this.state.countries.jsonURL, {method: "GET"})
      .then((response) => response.json())
      .then((responseData) => {
        console.log('Length: ' + responseData.RestResponse.result.length);
        let result = responseData.RestResponse.result;
        this.setState({
          dataSource: this.state.dataSource.cloneWithRows(result)
        });
      })
      .catch((error) => {
        console.log('ERROR');
        this.setState({countries: {error: error.message}})
      })
      .done(() => {
        this.setState({
          loadStatus: 'completed'
        });
      })
  }

  render() {
    console.log('Render invoked');
    if(this.state.error) {
      return (
        <PageError errorText={this.state.error}/>
      )
    } else {
      return (
        <View style={styles.container}>
          <Text>{ this.state.loadStatus }</Text>
          <ListView style={styles.listview}
                    dataSource={this.state.dataSource}
                    renderRow={(rowData, sectionID, rowID, highlightRow) => this.rowData(rowID, rowData)}
                    renderHeader={this.rowHeader.bind(this)}
          />
        </View>
      );
    }
  }

  rowData(rowID, rowData) {
    return (
      <ListRow index={rowID} name={rowData.name} alpha2_code={rowData.alpha2_code} alpha3_code={rowData.alpha3_code}/>);
  }

  rowHeader() {
    return (
      <ListHeader headerText={this.state.rowHeaderText} />
    )
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#F5FCFF',
    marginTop: 30,
  },
  listview: {
    flex: 1,
    borderWidth:1,
    borderColor:'#000000',
    alignSelf:'stretch',
    margin:10,
  },
  errorHeader: {
    fontSize: 26,
    textAlign: 'left',
    fontWeight: '200',
    color:'red',
    margin: 10,
    marginTop:50,
  },
  errorText: {
    fontSize: 20,
    textAlign: 'left',
    fontWeight: '300',
    color:'black',
    margin: 10,
  },
});

AppRegistry.registerComponent('SimpleListView', () => SimpleListView);

From code maintenance perspective, I create a separate directory structure for services and components.

  • src/services for all service code
  • src/components for all components


src/services/countries.js

import React, { Component } from 'react';

export default class CountriesService extends Component {
  constructor() {
    super();
    countries = {
      jsonURL: 'http://services.groupkt.com/country/get/all',
      error: '',
    };
    this.state = countries;
  }

  loadJSONData() {
    fetch(this.state.jsonURL, {method: "GET"})
      .then((response) => response.json())
      .then((responseData) => {
        console.log('Length: ' + responseData.RestResponse.result.length);
        let result = responseData.RestResponse.result;
        this.setState({
          dataSource: this.state.dataSource.cloneWithRows(result)
        });
      })
      .catch((error) => {
        console.log('ERROR');
        console.dir(error);
        this.setState({error: error.message})
      })
      .done(() => {
        this.setState({
          loadStatus: 'completed'
        });
      })
  }
}

Here's out List Header

src/components/list_header.js
/**
 * Sample React Native App
 * https://github.com/facebook/react-native
 * @flow
 */

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

export default class ListHeader extends Component {
  constructor(props) {
    super(props);
  }

  render() {
    return (
      <View style={styles.headerRow}>
        <Text style={styles.headerRowText}>{this.props.headerText}</Text>
      </View>);
  }
}

const styles = StyleSheet.create({
  headerRow: {
    flex: 1,
    backgroundColor: 'gray',
  },
  headerRowText: {
    fontSize: 25,
    textAlign: 'left',
    fontWeight: '300',
    color:'white',
    margin: 10,
  }
});

Each row is an instance of this class

src/components/list_row.js

/**
 * Sample React Native App
 * https://github.com/facebook/react-native
 * @flow
 */

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

export default class ListRow extends Component {
  constructor(props) {
    super(props);
  }

  styleRow() {
    if(this.props.index %2 == 0) {
      return styles.evenContainer
    }
    return styles.oddContainer;
  }

  render() {
    return (
      <View style={this.styleRow()}>
        <Text style={styles.rowTitle}>{this.props.name}</Text>
        <Text style={styles.rowText}>{this.props.alpha2_code}</Text>
        <Text style={styles.rowText}>{this.props.alpha3_code}</Text>
      </View>);
  }
}

const styles = StyleSheet.create({
  evenContainer: {
    flex: 1,
    backgroundColor: 'white',
  },
  oddContainer: {
    flex: 1,
    backgroundColor: 'lightgray',
  },
  rowTitle: {
    fontSize: 18,
    fontWeight: '200',
    textAlign: 'left',
    margin: 20,
  },
  rowText: {
    textAlign: 'left',
    color: '#333333',
    marginLeft: 25,
  },
});

All errors can be contained in this component

src/components/page_error.js
/**
 * Sample React Native App
 * https://github.com/facebook/react-native
 * @flow
 */

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

export default class PageError extends Component {
  constructor(props) {
    super(props);
  }

  styleRow() {
    if(this.props.index %2 == 0) {
      return styles.evenContainer
    }
    return styles.oddContainer;
  }

  render() {
    return (
      <View>
        <Text style={styles.errorHeader}>
          ERROR
        </Text>
        <Text style={styles.errorText}>{this.props.errorText}</Text>
      </View>);
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: 'white',
  },
  errorHeader: {
    fontSize: 26,
    textAlign: 'left',
    fontWeight: '200',
    color:'red',
    margin: 10,
    marginTop:50,
  },
  errorText: {
    fontSize: 20,
    textAlign: 'left',
    fontWeight: '300',
    color:'black',
    margin: 10,
  },
});

No comments:

Post a Comment