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

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

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

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

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

01
02
03
04
05
06
07
08
09
10
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リクエストを行うメソッドを用意する。

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
_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」メソッドを用意する。

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
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()内で実行されるようにする。

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
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でアプリを実行すると、人気・議論中それぞれに分かれた記事リストが表示される。