React Native FlatList: データなし時の表示を極める ListEmptyComponent 徹底解説
FlatList
はReact Nativeでリスト表示を効率的に行うためのコンポーネントですが、表示するデータがない場合に何も表示されないのがデフォルトの動作です。
ListEmptyComponent
プロパティは、この「表示するデータがない」状態のときに、代わりに表示させたいコンポーネントを指定するためのものです。
ユースケース
例えば、以下のような場合に非常に役立ちます。
- ローディング状態を表示する
データがまだ読み込まれていない初期状態や、検索結果がない場合に「検索中...」といった表示をする(ただし、ローディング表示には別の方法を用いることも多いです)。 - データ追加を促す
「新しいアイテムを追加してください」といった指示や、データ入力フォームへのリンクを表示する。 - データが存在しないことをユーザーに伝える
「アイテムが見つかりませんでした」や「データがありません」といったメッセージを表示する。
使用例
import React from 'react';
import { FlatList, Text, View, StyleSheet } from 'react-native';
const MyList = ({ data }) => {
return (
<FlatList
data={data}
renderItem={({ item }) => <Text style={styles.item}>{item.title}</Text>}
keyExtractor={item => item.id}
ListEmptyComponent={() => (
<View style={styles.emptyContainer}>
<Text style={styles.emptyText}>表示するデータがありません。</Text>
<Text style={styles.emptySubText}>新しいアイテムを追加してみましょう!</Text>
</View>
)}
/>
);
};
const styles = StyleSheet.create({
item: {
padding: 20,
fontSize: 18,
borderBottomWidth: 1,
borderBottomColor: '#ccc',
},
emptyContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
padding: 20,
},
emptyText: {
fontSize: 20,
color: '#888',
marginBottom: 10,
},
emptySubText: {
fontSize: 16,
color: '#aaa',
},
});
export default MyList;
上記の例では、data
配列が空の場合にListEmptyComponent
に指定されたView
とText
が表示されます。
注意点
ListEmptyComponent
自体は、FlatList
のスクロール可能な領域内に表示されます。そのため、コンポーネントの高さやスタイルによっては、画面全体に表示されない場合もあります。必要に応じて、FlatList
のスタイルやコンテナのスタイルを調整してください。ListEmptyComponent
は、data
プロパティに渡された配列が空の場合にのみ表示されます。
ListEmptyComponentが表示されない
考えられる原因と解決策
-
条件付きレンダリングの間違い
FlatList
自体をデータがあるかないかで条件付きレンダリングしている場合、ListEmptyComponent
の役割が失われます。- 解決策
ListEmptyComponent
を使用する目的は、FlatList
が存在するがデータがない場合に代替コンテンツを表示することなので、FlatList
の有無をデータで制御するのではなく、ListEmptyComponent
を使用するようにコードを書き換えてください。
// 誤った例 {data.length > 0 ? ( <FlatList data={data} renderItem={...} /> ) : ( <Text>データがありません。</Text> )} // 良い例 <FlatList data={data} renderItem={...} ListEmptyComponent={() => <Text>データがありません。</Text>} />
-
コンテナのスタイル設定の問題
FlatList
やその親コンポーネントのスタイルが適切に設定されていないと、ListEmptyComponent
が表示されていても画面上で見えないことがあります。特にflex
プロパティや高さ(height
)が不足している場合によく起こります。- 解決策
FlatList
の親コンポーネントが十分なスペースを確保しているか確認してください。多くの場合、flex: 1
を設定することで解決します。また、ListEmptyComponent
自体にも十分な高さを与える必要があります。
// FlatListの親コンポーネント <View style={{ flex: 1 }}> {/* 親が十分な高さを確保しているか確認 */} <FlatList data={[]} renderItem={...} ListEmptyComponent={() => ( <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}> <Text>データがありません。</Text> </View> )} /> </View>
または、
FlatList
のcontentContainerStyle
を使ってListEmptyComponent
が領域を占めるようにします。<FlatList data={[]} renderItem={...} contentContainerStyle={{ flexGrow: 1, justifyContent: 'center', alignItems: 'center' }} // これが重要 ListEmptyComponent={() => ( <View> <Text>データがありません。</Text> </View> )} />
flexGrow: 1
は、コンテナが利用可能なスペースを全て占めるようにします。 -
ListEmptyComponent
は、FlatList
のdata
プロパティに渡された配列が完全に空([]
)の場合にのみ表示されます。- もし
data
がnull
やundefined
の場合、あるいは空ではないが要素がレンダリングされないような状態(例: フィルターによって要素がなくなる)の場合、ListEmptyComponent
は表示されません。 - 解決策
FlatList
に渡すdata
が、本当にデータがない場合に空の配列([]
)になっているかを確認してください。非同期処理でデータを取得する場合、初期値として空の配列を設定し、データがない場合に空の配列を返すように徹底します。
// 誤った例 (dataがnullの場合、ListEmptyComponentは表示されない) const MyList = ({ data }) => { // dataがまだロードされていない、またはnullの場合 return ( <FlatList data={data} // dataがnullやundefinedの可能性がある renderItem={...} ListEmptyComponent={...} /> ); }; // 良い例 const MyList = ({ data }) => { const actualData = data || []; // データがない場合は空の配列を設定 return ( <FlatList data={actualData} renderItem={...} ListEmptyComponent={...} /> ); };
ListEmptyComponentのスタイルが意図通りにならない
考えられる原因と解決策
-
ListEmptyComponent自身のスタイル不足
ListEmptyComponent
に指定するコンポーネントが、その内容を適切に表示するためのスタイル(例:height
,width
,padding
)を持っていない可能性があります。- 解決策
ListEmptyComponent
として渡すコンポーネントに、必要なスタイルを明示的に指定してください。
-
FlatListのスクロール動作の影響
ListEmptyComponent
はFlatList
のコンテンツ領域に表示されるため、FlatList
がスクロール可能になっていると、コンポーネント全体が見えない場合があります。- 解決策
前述のcontentContainerStyle
を使用し、justifyContent
やalignItems
で中央寄せにするなど、ListEmptyComponent
がリスト領域内でどのように配置されるかを制御します。flexGrow: 1
を追加することで、ListEmptyComponent
が利用可能なすべての垂直方向のスペースを占めるようにし、中央に配置しやすくなります。
invertedプロパティとの併用で問題が発生する
考えられる原因と解決策
- invertedとListEmptyComponentの表示方向の不整合
FlatList
にinverted={true}
を設定すると、リストの表示順序が逆になりますが、古いReact NativeのバージョンではListEmptyComponent
が逆さまに表示される、または意図しない位置に表示されるバグがありました。- 解決策
React Nativeのバージョンを最新に保つことが最も重要です。この問題は過去のバージョンで修正されています。もし最新バージョンでも問題が発生する場合は、GitHubのReact Nativeリポジトリで関連するIssueを探したり、コミュニティで相談したりすることをお勧めします。一時的なワークアラウンドとしては、inverted
を使用しない代替手段を検討するか、ListEmptyComponent
の内部でスタイルを調整して無理やり修正する方法が考えられますが、通常はバージョンアップで解決します。
パフォーマンスの問題 (稀だが念のため)
考えられる原因と解決策
- ListEmptyComponentが複雑すぎる
ListEmptyComponent
自体が非常に複雑なレンダリングロジックや大量のコンポーネントを含んでいる場合、それがレンダリングされる際にわずかなパフォーマンスオーバーヘッドが発生する可能性があります。- 解決策
通常は問題になりませんが、もし非常に複雑なコンポーネントをListEmptyComponent
に指定する必要がある場合は、そのコンポーネント自体が最適化されているか(例:memo
の使用)確認してください。
ListEmptyComponent
のトラブルシューティングのほとんどは、以下の点を確認することで解決します。
FlatList
のdata
プロパティが本当に空の配列([]
)になっているか? (最も一般的)FlatList
およびListEmptyComponent
の親コンポーネントが十分なスペースを確保しているか? (flex: 1
やheight
の確認)ListEmptyComponent
のスタイルが適切に設定されているか? (contentContainerStyle
の活用)- React Nativeのバージョンは最新か? (既知のバグの修正)
ListEmptyComponent
は、データが空の場合にユーザーに情報を提供したり、次のアクションを促したりするのに非常に役立ちます。
基本的な「データがありません」メッセージ
最もシンプルで一般的な例です。
import React from 'react';
import { FlatList, Text, View, StyleSheet } from 'react-native';
const BasicEmptyList = ({ items }) => {
return (
<FlatList
data={items} // itemsが [] の場合にListEmptyComponentが表示される
renderItem={({ item }) => <Text style={styles.item}>{item.name}</Text>}
keyExtractor={item => item.id}
ListEmptyComponent={() => (
<View style={styles.emptyContainer}>
<Text style={styles.emptyText}>データがありません。</Text>
</View>
)}
/>
);
};
const styles = StyleSheet.create({
item: {
padding: 15,
fontSize: 16,
borderBottomWidth: 1,
borderBottomColor: '#eee',
},
emptyContainer: {
flex: 1, // 親のFlatListがflex: 1の場合、これが全体を占める
justifyContent: 'center', // 垂直方向中央
alignItems: 'center', // 水平方向中央
padding: 20,
minHeight: 200, // ListEmptyComponentがある程度の高さを占めるようにする
},
emptyText: {
fontSize: 18,
color: '#888',
},
});
export default BasicEmptyList;
// 使用例
// <BasicEmptyList items={[]} /> // -> "データがありません。"と表示される
// <BasicEmptyList items={[{id: '1', name: 'りんご'}]} /> // -> りんごが表示される
ポイント
minHeight
を設定することで、FlatList
の高さが動的に変わる場合でも、ListEmptyComponent
がある程度の領域を確保するようにしています。emptyContainer
にflex: 1
とjustifyContent: 'center'
,alignItems: 'center'
を設定することで、コンテナの中心にテキストを配置しています。
データ追加を促すメッセージとボタン
ユーザーに次のアクション(データの追加など)を促す例です。
import React from 'react';
import { FlatList, Text, View, StyleSheet, TouchableOpacity, Alert } from 'react-native';
const AddItemEmptyList = ({ items, onAddItem }) => {
return (
<FlatList
data={items}
renderItem={({ item }) => <Text style={styles.item}>{item.name}</Text>}
keyExtractor={item => item.id}
ListEmptyComponent={() => (
<View style={styles.emptyContainer}>
<Text style={styles.emptyText}>リストにアイテムがありません。</Text>
<Text style={styles.emptySubText}>下のボタンを押して新しいアイテムを追加しましょう!</Text>
<TouchableOpacity style={styles.addButton} onPress={onAddItem}>
<Text style={styles.addButtonText}>アイテムを追加</Text>
</TouchableOpacity>
</View>
)}
/>
);
};
const styles = StyleSheet.create({
item: {
padding: 15,
fontSize: 16,
borderBottomWidth: 1,
borderBottomColor: '#eee',
},
emptyContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
padding: 20,
minHeight: 250,
},
emptyText: {
fontSize: 18,
color: '#888',
marginBottom: 10,
},
emptySubText: {
fontSize: 14,
color: '#aaa',
marginBottom: 20,
textAlign: 'center',
},
addButton: {
backgroundColor: '#007bff',
paddingVertical: 10,
paddingHorizontal: 20,
borderRadius: 5,
},
addButtonText: {
color: '#fff',
fontSize: 16,
fontWeight: 'bold',
},
});
export default AddItemEmptyList;
// 使用例 (App.jsなど)
// const [myItems, setMyItems] = React.useState([]);
// const handleAddItem = () => {
// Alert.alert("追加", "アイテム追加画面に遷移します");
// // 例: 実際にはナビゲーションやモーダル表示など
// const newItemId = String(myItems.length + 1);
// setMyItems(prevItems => [...prevItems, { id: newItemId, name: `新しいアイテム ${newItemId}` }]);
// };
// <AddItemEmptyList items={myItems} onAddItem={handleAddItem} />
ポイント
onAddItem
プロパティを渡すことで、親コンポーネントでボタン押下時の処理を定義できます。TouchableOpacity
を使って、タップ可能なボタンを配置しています。
検索結果がない場合のメッセージ
検索機能を持つリストで、検索結果が空の場合に表示する例です。
import React from 'react';
import { FlatList, Text, View, StyleSheet, TextInput } from 'react-native';
const SearchEmptyList = () => {
const [searchText, setSearchText] = React.useState('');
const allItems = [
{ id: '1', name: 'Apple' },
{ id: '2', name: 'Banana' },
{ id: '3', name: 'Cherry' },
{ id: '4', name: 'Date' },
];
const filteredItems = allItems.filter(item =>
item.name.toLowerCase().includes(searchText.toLowerCase())
);
return (
<View style={styles.container}>
<TextInput
style={styles.searchInput}
placeholder="アイテムを検索..."
value={searchText}
onChangeText={setSearchText}
/>
<FlatList
data={filteredItems}
renderItem={({ item }) => <Text style={styles.item}>{item.name}</Text>}
keyExtractor={item => item.id}
ListEmptyComponent={() => (
<View style={styles.emptyContainer}>
{searchText ? (
<Text style={styles.emptyText}>"{searchText}" に一致するアイテムは見つかりませんでした。</Text>
) : (
<Text style={styles.emptyText}>ここに検索結果が表示されます。</Text>
)}
<Text style={styles.emptySubText}>検索ワードを変更してみてください。</Text>
</View>
)}
/>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
paddingTop: 20,
},
searchInput: {
height: 40,
borderColor: '#ccc',
borderWidth: 1,
borderRadius: 8,
paddingHorizontal: 10,
marginHorizontal: 15,
marginBottom: 15,
},
item: {
padding: 15,
fontSize: 16,
borderBottomWidth: 1,
borderBottomColor: '#eee',
marginHorizontal: 15,
},
emptyContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
padding: 20,
minHeight: 200,
},
emptyText: {
fontSize: 18,
color: '#888',
marginBottom: 10,
textAlign: 'center',
},
emptySubText: {
fontSize: 14,
color: '#aaa',
textAlign: 'center',
},
});
export default SearchEmptyList;
ポイント
searchText
が空の場合は「ここに検索結果が表示されます。」と表示し、searchText
があるのにfilteredItems
が空の場合は「一致するアイテムは見つかりませんでした。」と表示します。searchText
の状態に応じて、表示するメッセージを切り替えています。
contentContainerStyleを使ってListEmptyComponentを中央に配置する
FlatList
のコンテンツが少なくても、ListEmptyComponent
が画面全体の中央に表示されるようにする一般的なテクニックです。
import React from 'react';
import { FlatList, Text, View, StyleSheet } from 'react-native';
const CenteredEmptyList = ({ items }) => {
return (
<FlatList
data={items}
renderItem={({ item }) => <Text style={styles.item}>{item.name}</Text>}
keyExtractor={item => item.id}
// ここが重要!
contentContainerStyle={items.length === 0 && styles.emptyListContainer}
ListEmptyComponent={() => (
<View> {/* flex: 1 は不要。contentContainerStyleで制御する */}
<Text style={styles.emptyText}>まだアイテムがありません。</Text>
<Text style={styles.emptySubText}>何か追加してみましょう。</Text>
</View>
)}
/>
);
};
const styles = StyleSheet.create({
item: {
padding: 15,
fontSize: 16,
borderBottomWidth: 1,
borderBottomColor: '#eee',
},
// ListEmptyComponentが表示される際に適用されるスタイル
emptyListContainer: {
flexGrow: 1, // 親の利用可能なスペースを全て占める
justifyContent: 'center', // 垂直方向中央
alignItems: 'center', // 水平方向中央
},
emptyText: {
fontSize: 18,
color: '#888',
marginBottom: 5,
},
emptySubText: {
fontSize: 14,
color: '#aaa',
},
});
export default CenteredEmptyList;
// 使用例
// <CenteredEmptyList items={[]} /> // -> 画面中央にメッセージが表示される
flexGrow: 1
は、FlatList
の内部コンテナが、利用可能な垂直方向のスペースを全て占めるようにします。これにより、justifyContent: 'center'
が効果を発揮し、ListEmptyComponent
が中央に配置されます。items.length === 0
の場合にのみemptyListContainer
スタイルを適用することで、リストにデータがある場合は通常のスクロール動作を維持し、データがない場合にのみ中央寄せを実現します。FlatList
のcontentContainerStyle
プロパティは、リストのコンテンツを囲む内部のScrollView
コンポーネントにスタイルを適用します。
dataの長さに応じた条件付きレンダリング
これは最も直接的な代替手段で、FlatList
のdata
プロパティの長さをチェックし、それに基づいて表示する内容を切り替えます。
import React from 'react';
import { View, Text, FlatList, StyleSheet } from 'react-native';
const ConditionalRenderList = ({ items, isLoading }) => {
if (isLoading) {
return (
<View style={styles.loadingContainer}>
<Text style={styles.loadingText}>データを読み込み中...</Text>
</View>
);
}
if (items.length === 0) {
return (
<View style={styles.emptyContainer}>
<Text style={styles.emptyText}>まだアイテムがありません。</Text>
<Text style={styles.emptySubText}>新しいアイテムを追加してみましょう。</Text>
</View>
);
}
return (
<FlatList
data={items}
renderItem={({ item }) => <Text style={styles.item}>{item.name}</Text>}
keyExtractor={item => item.id}
/>
);
};
const styles = StyleSheet.create({
loadingContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
loadingText: {
fontSize: 18,
color: '#666',
},
emptyContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
padding: 20,
},
emptyText: {
fontSize: 18,
color: '#888',
marginBottom: 10,
},
emptySubText: {
fontSize: 14,
color: '#aaa',
},
item: {
padding: 15,
fontSize: 16,
borderBottomWidth: 1,
borderBottomColor: '#eee',
},
});
export default ConditionalRenderList;
// 使用例
// <ConditionalRenderList items={[]} isLoading={false} /> // データなし
// <ConditionalRenderList items={[]} isLoading={true} /> // ローディング中
// <ConditionalRenderList items={[{id: '1', name: 'Test'}]} isLoading={false} /> // データあり
利点
- コンポーネントの分離
FlatList
自体のレンダリングロジックとは切り離して、それぞれの状態に対応するコンポーネントを設計できます。 - より柔軟な制御
ローディング状態、エラー状態、データなしの状態など、複数の状態をFlatList
とは完全に独立して制御できます。
欠点
FlatList
のコンテンツ領域の中央に表示する、といった細かな配置調整がListEmptyComponent
を使う場合よりも少し手間がかかることがあります。(親コンポーネントのスタイルを調整する必要があるため)- コード量が増える可能性があります。
ListHeaderComponentまたはListFooterComponentを利用する(稀なケース)
これは一般的な代替手段ではありませんが、特殊なレイアウト要件がある場合に検討されることがあります。例えば、リストのヘッダーやフッターに「データがありません」というメッセージを表示し、同時にFlatList
自体は常に表示させておきたい場合などです。
import React from 'react';
import { View, Text, FlatList, StyleSheet } from 'react-native';
const HeaderFooterEmptyList = ({ items }) => {
return (
<FlatList
data={items.length > 0 ? items : []} // データがない場合は空の配列を渡す
renderItem={({ item }) => <Text style={styles.item}>{item.name}</Text>}
keyExtractor={item => item.id}
ListHeaderComponent={() => (
items.length === 0 ? (
<View style={styles.emptyHeaderContainer}>
<Text style={styles.emptyHeaderText}>現在のリストは空です。</Text>
<Text style={styles.emptyHeaderSubText}>アイテムを追加してください。</Text>
</View>
) : null
)}
// ListEmptyComponentは使用しない
/>
);
};
const styles = StyleSheet.create({
emptyHeaderContainer: {
padding: 20,
alignItems: 'center',
backgroundColor: '#f8f8f8',
borderBottomWidth: 1,
borderBottomColor: '#eee',
},
emptyHeaderText: {
fontSize: 18,
color: '#888',
marginBottom: 5,
},
emptyHeaderSubText: {
fontSize: 14,
color: '#aaa',
},
item: {
padding: 15,
fontSize: 16,
borderBottomWidth: 1,
borderBottomColor: '#eee',
},
});
export default HeaderFooterEmptyList;
利点
FlatList
のヘッダーやフッターの一部として「データなし」メッセージを統合したい場合に有効です。
欠点
- リストにデータがない場合でもヘッダーやフッターが表示され続けるため、UIの意図と合わない場合があります。
ListEmptyComponent
のように自動的に中央に配置されるわけではないため、スタイル調整がより必要です。
カスタムの空状態コンポーネントをPropsとして渡す
これは、ListEmptyComponent
の概念をより抽象化し、再利用可能なコンポーネントとしてラップするアプローチです。