通常React Nativeでリストを表示する場合、FlatListコンポーネントが使われるが、SectionListではリスト毎にタイトルを設定したり、個別の関数を適用したりすることができる。

以前、下記の記事で作ったニュースアプリに手を加える形で、SectionListの使い方を見ていこう。

RedditのスレをAPIで取得してリスト形式で表示するアプリだが、このリストを「人気」、「議論中」とそれぞれリストを分割して表示する。

それぞれのAPIに対応するURLを用意する

まずはAPIに対応するURLを配列形式で用意する。

let list = [
  {
    uri: "https://www.reddit.com/r/newsokur/hot.json",
    title: "人気"
  },
  {
    uri: "https://www.reddit.com/r/newsokur/controversial.json",
    title: "議論中"
  }
];

Promise形式でHTTPリクエストを行う

複数のURL(スレッド)からデータを取得するので、Promise形式でHTTPリクエストを行うメソッドを用意する。

_fetchThread(item) {
  return new Promise((resolve, reject) => {
    fetch(item.uri)
      .then((response) => response.json())
      .then((responseJson) => {
        let threads = responseJson.data.children.slice(0, 5);
        threads = threads.map(i => {
          i.key = i.data.url;
          return i;
        })

        return resolve({data: threads, title: item.title});
      })
      .catch((error) => {
        return reject(error);
      })
  });
}

この_fetchThreadメソッドを、非同期に並列で実行するためにPromise.all()を使った「fetchThread」メソッドを用意する。

fetchThread() {
  let list = [
    {
      uri: "https://www.reddit.com/r/newsokur/hot.json",
      title: "人気"
    },
    {
      uri: "https://www.reddit.com/r/newsokur/controversial.json",
      title: "議論中"
    }
  ];

  Promise.all(list.map(i => this._fetchThread(i)))
    .then(r => {
      this.setState({threads: r});
      this.setState({isLoading: false});
    }).catch(e => {
      console.warn(e);
    })
}

リクエストが成功するとthenメソッドが呼び出され、state変数に記事データがセットされる。

コード全文

ここまでで用意したfetchThreadメソッドは、マウント時に実行されるようcompoentDidMount()内で実行されるようにする。

import React, { Component } from 'react';
import {
  StyleSheet, Text, View, SectionList, Image, Dimensions, ActivityIndicator
} from 'react-native';

export default class App extends Component {
  constructor() {
    super();
    this.state = {
      isLoading: true,
      threads: [],
    }
  }

  componentDidMount() {
    this.fetchThread();
  }

  _fetchThread(item) {
    return new Promise((resolve, reject) => {
      fetch(item.uri)
        .then((response) => response.json())
        .then((responseJson) => {
          let threads = responseJson.data.children.slice(0, 5);
          threads = threads.map(i => {
            i.key = i.data.url;
            return i;
          })

          return resolve({data: threads, title: item.title});
        })
        .catch((error) => {
          return reject(error);
        })
    });
  }

  fetchThread() {
    let list = [
      {
        uri: "https://www.reddit.com/r/newsokur/hot.json",
        title: "人気"
      },
      {
        uri: "https://www.reddit.com/r/newsokur/controversial.json",
        title: "議論中"
      }
    ];

    Promise.all(list.map(i => this._fetchThread(i)))
      .then(r => {
        this.setState({threads: r});
        this.setState({isLoading: false});
      }).catch(e => {
        console.warn(e);
      })
  }

  render() {
    const { threads, isLoading } = this.state;
    const { width } = Dimensions.get('window');

    return(
      <View style={styles.container}>
        {isLoading ? <ActivityIndicator /> :
        <SectionList
          renderItem={thread => {
            return (
              <View style={{
                flex: 1,
                flexDirection: 'row',
                width: '100%'
              }}>
                <Image style={{width: 50, height: 50}} source={{uri: thread.item.data.thumbnail}} />
                <Text style={{width: width - 50}} key={thread.key}>{thread.item.data.title}</Text>
              </View>
            );
          }}
          renderSectionHeader={({section}) => <Text>{section.title}</Text>}
          sections={threads}
        />
        }
      </View>
    )
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#F5FCFF',
    paddingTop: 20,
  }
});

expo startでアプリを実行すると、人気・議論中それぞれに分かれた記事リストが表示される。