通常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でアプリを実行すると、人気・議論中それぞれに分かれた記事リストが表示される。