React Native FlatList: データなし時の表示を極める ListEmptyComponent 徹底解説

2025-05-31

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に指定されたViewTextが表示されます。

注意点

  • 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>
    

    または、FlatListcontentContainerStyleを使ってListEmptyComponentが領域を占めるようにします。

    <FlatList
      data={[]}
      renderItem={...}
      contentContainerStyle={{ flexGrow: 1, justifyContent: 'center', alignItems: 'center' }} // これが重要
      ListEmptyComponent={() => (
        <View>
          <Text>データがありません。</Text>
        </View>
      )}
    />
    

    flexGrow: 1は、コンテナが利用可能なスペースを全て占めるようにします。

    • ListEmptyComponentは、FlatListdataプロパティに渡された配列が完全に空[])の場合にのみ表示されます。
    • もしdatanullundefinedの場合、あるいは空ではないが要素がレンダリングされないような状態(例: フィルターによって要素がなくなる)の場合、ListEmptyComponentは表示されません。
    • 解決策
      FlatListに渡すdataが、本当にデータがない場合に空の配列([])になっているかを確認してください。非同期処理でデータを取得する場合、初期値として空の配列を設定し、データがない場合に空の配列を返すように徹底します。
    // 誤った例 (dataがnullの場合、ListEmptyComponentは表示されない)
    const MyList = ({ data }) => {
      // dataがまだロードされていない、またはnullの場合
      return (
        <FlatList
          data={data} // datanullundefinedの可能性がある
          renderItem={...}
          ListEmptyComponent={...}
        />
      );
    };
    
    // 良い例
    const MyList = ({ data }) => {
      const actualData = data || []; // データがない場合は空の配列を設定
      return (
        <FlatList
          data={actualData}
          renderItem={...}
          ListEmptyComponent={...}
        />
      );
    };
    

ListEmptyComponentのスタイルが意図通りにならない

考えられる原因と解決策

  • ListEmptyComponent自身のスタイル不足

    • ListEmptyComponentに指定するコンポーネントが、その内容を適切に表示するためのスタイル(例: height, width, padding)を持っていない可能性があります。
    • 解決策
      ListEmptyComponentとして渡すコンポーネントに、必要なスタイルを明示的に指定してください。
  • FlatListのスクロール動作の影響

    • ListEmptyComponentFlatListのコンテンツ領域に表示されるため、FlatListがスクロール可能になっていると、コンポーネント全体が見えない場合があります。
    • 解決策
      前述のcontentContainerStyleを使用し、justifyContentalignItemsで中央寄せにするなど、ListEmptyComponentがリスト領域内でどのように配置されるかを制御します。flexGrow: 1を追加することで、ListEmptyComponentが利用可能なすべての垂直方向のスペースを占めるようにし、中央に配置しやすくなります。

invertedプロパティとの併用で問題が発生する

考えられる原因と解決策

  • invertedとListEmptyComponentの表示方向の不整合
    • FlatListinverted={true}を設定すると、リストの表示順序が逆になりますが、古いReact NativeのバージョンではListEmptyComponentが逆さまに表示される、または意図しない位置に表示されるバグがありました。
    • 解決策
      React Nativeのバージョンを最新に保つことが最も重要です。この問題は過去のバージョンで修正されています。もし最新バージョンでも問題が発生する場合は、GitHubのReact Nativeリポジトリで関連するIssueを探したり、コミュニティで相談したりすることをお勧めします。一時的なワークアラウンドとしては、invertedを使用しない代替手段を検討するか、ListEmptyComponentの内部でスタイルを調整して無理やり修正する方法が考えられますが、通常はバージョンアップで解決します。

パフォーマンスの問題 (稀だが念のため)

考えられる原因と解決策

  • ListEmptyComponentが複雑すぎる
    • ListEmptyComponent自体が非常に複雑なレンダリングロジックや大量のコンポーネントを含んでいる場合、それがレンダリングされる際にわずかなパフォーマンスオーバーヘッドが発生する可能性があります。
    • 解決策
      通常は問題になりませんが、もし非常に複雑なコンポーネントをListEmptyComponentに指定する必要がある場合は、そのコンポーネント自体が最適化されているか(例: memoの使用)確認してください。

ListEmptyComponentのトラブルシューティングのほとんどは、以下の点を確認することで解決します。

  1. FlatListdataプロパティが本当に空の配列([])になっているか? (最も一般的)
  2. FlatListおよびListEmptyComponentの親コンポーネントが十分なスペースを確保しているか? (flex: 1heightの確認)
  3. ListEmptyComponentのスタイルが適切に設定されているか? (contentContainerStyleの活用)
  4. 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がある程度の領域を確保するようにしています。
  • emptyContainerflex: 1justifyContent: '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スタイルを適用することで、リストにデータがある場合は通常のスクロール動作を維持し、データがない場合にのみ中央寄せを実現します。
  • FlatListcontentContainerStyleプロパティは、リストのコンテンツを囲む内部のScrollViewコンポーネントにスタイルを適用します。


dataの長さに応じた条件付きレンダリング

これは最も直接的な代替手段で、FlatListdataプロパティの長さをチェックし、それに基づいて表示する内容を切り替えます。

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の概念をより抽象化し、再利用可能なコンポーネントとしてラップするアプローチです。