LoginSignup
15
14

More than 5 years have passed since last update.

Laravel + ReactNative(FlatList)で無限スクロール

Last updated at Posted at 2018-10-29

無限スクロールとは?

正式な呼び方はわかりませんが、リストが最後まで来たら、次のページを読み込む処理。SNSのタイムライン読み込みとかで普通に見るやつ。
サーバ側との連携が不可欠なので、いつも使うLaravel + ReactNativeで試す。

Laravel側の実装

まず、Laravel側の準備。とりあえず標準で準備されているUser関連のデータを利用する。

マイグレーション

.envでデータベースの設定をしたら、migrateとするだけでusersテーブルが生成されます。

php artisan migrate

データ生成

最初からあるFactoryを使ってデータを生成します。
DatabaseSeeder.phpに直接下記のように記述します。数はお好みで。

database/seeds/DatabaseSeeder.php
<?php

use Illuminate\Database\Seeder;

class DatabaseSeeder extends Seeder
{
    public function run()
    {
        //Userデータを100件作る
        factory(App\User::class,100)->create();
    }
}

seederを実行します。

php artisan db:seed

これでデータができました。

ルート定義

続いて値をJSONで返すAPIを定義していきます。まずはルートから。
/api/usersでアクセスしたら値を返す想定。

web.php
<?php

use Illuminate\Http\Request;

Route::get('/users', 'UserController@index');

コントローラー定義

続いてControllerの作成。

php artisan make:controller UserController

10件ずつページネーションするのは下記のような記述でOK。

UserController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class UserController extends Controller
{
    public function index(){
        $users = \App\User::paginate(10);
        return \Response::json($users);
    }
}

リクエスト

問題はLaravelがどうページネーションをしてくれてるか?

とりあえず起動。

php artisan serve

普通にリクエストしてみる。

どうやら1ページ目が帰ってきてるよう。
重要なことは、Userデータ以外にもnext_pageやlast_pageなどが帰ってきてること。

特に何ページあるかは重要なのでlast_pageは重要な値。

で、どうやってページを送るのか?

{"current_page":1,"data":[{"id":1,"name":"Toney Marvin III","email":"dolly17@example.com","email_verified_at":null,"created_at":"2018-10-29 21:47:36","updated_at":"2018-10-29 21:47:36"},{"id":2,"name":"Kayli Walsh","email":"xgibson@example.com","email_verified_at":null,"created_at":"2018-10-29 21:47:36","updated_at":"2018-10-29 21:47:36"},{"id":3,"name":"Jamal Schmitt","email":"fjones@example.net","email_verified_at":null,"created_at":"2018-10-29 21:47:36","updated_at":"2018-10-29 21:47:36"},{"id":4,"name":"Kurtis Feeney","email":"arnaldo.mante@example.org","email_verified_at":null,"created_at":"2018-10-29 21:47:36","updated_at":"2018-10-29 21:47:36"},{"id":5,"name":"Alexandro Walsh","email":"skshlerin@example.org","email_verified_at":null,"created_at":"2018-10-29 21:47:36","updated_at":"2018-10-29 21:47:36"},{"id":6,"name":"Jessyca Towne","email":"wilmer.labadie@example.com","email_verified_at":null,"created_at":"2018-10-29 21:47:36","updated_at":"2018-10-29 21:47:36"},{"id":7,"name":"Favian Parker MD","email":"ccummerata@example.org","email_verified_at":null,"created_at":"2018-10-29 21:47:36","updated_at":"2018-10-29 21:47:36"},{"id":8,"name":"Osbaldo Jenkins","email":"ettie.parker@example.com","email_verified_at":null,"created_at":"2018-10-29 21:47:36","updated_at":"2018-10-29 21:47:36"},{"id":9,"name":"Dr. Beryl Nienow V","email":"justice72@example.net","email_verified_at":null,"created_at":"2018-10-29 21:47:36","updated_at":"2018-10-29 21:47:36"},{"id":10,"name":"Mr. London Spinka","email":"mazie.luettgen@example.com","email_verified_at":null,"created_at":"2018-10-29 21:47:36","updated_at":"2018-10-29 21:47:36"}],"first_page_url":"http:\/\/localhost:8000\/api\/users?page=1","from":1,"last_page":10,"last_page_url":"http:\/\/localhost:8000\/api\/users?page=10","next_page_url":"http:\/\/localhost:8000\/api\/users?page=2","path":"http:\/\/localhost:8000\/api\/users","per_page":10,"prev_page_url":null,"to":10,"total":100}

ページ送り(page=2)

page=ページ番号というパラメータを付けて送ってみると、そのページが返る。素晴らしい。
ということはpageをlast_pageまでカウントしていけば、ページ送り(というか、ページの追加)ができるということです。

{"current_page":2,"data":[{"id":11,"name":"Dean Krajcik","email":"minnie.harvey@example.com","email_verified_at":null,"created_at":"2018-10-29 21:47:36","updated_at":"2018-10-29 21:47:36"},{"id":12,"name":"Ms. Rachelle Dibbert PhD","email":"marilou19@example.com","email_verified_at":null,"created_at":"2018-10-29 21:47:36","updated_at":"2018-10-29 21:47:36"},{"id":13,"name":"Gerson Emmerich Sr.","email":"mikel.schuster@example.net","email_verified_at":null,"created_at":"2018-10-29 21:47:36","updated_at":"2018-10-29 21:47:36"},{"id":14,"name":"Abner Kunde","email":"hmcclure@example.net","email_verified_at":null,"created_at":"2018-10-29 21:47:36","updated_at":"2018-10-29 21:47:36"},{"id":15,"name":"Prof. Sophia Rogahn V","email":"jaskolski.robin@example.org","email_verified_at":null,"created_at":"2018-10-29 21:47:36","updated_at":"2018-10-29 21:47:36"},{"id":16,"name":"Prof. Marshall Rodriguez II","email":"srowe@example.com","email_verified_at":null,"created_at":"2018-10-29 21:47:36","updated_at":"2018-10-29 21:47:36"},{"id":17,"name":"Prof. Kian O'Keefe","email":"beatrice.reichert@example.org","email_verified_at":null,"created_at":"2018-10-29 21:47:36","updated_at":"2018-10-29 21:47:36"},{"id":18,"name":"Prof. Mary Swaniawski MD","email":"mrenner@example.org","email_verified_at":null,"created_at":"2018-10-29 21:47:36","updated_at":"2018-10-29 21:47:36"},{"id":19,"name":"Mrs. Darby Homenick","email":"whowe@example.com","email_verified_at":null,"created_at":"2018-10-29 21:47:36","updated_at":"2018-10-29 21:47:36"},{"id":20,"name":"Javon Price III","email":"mohammed96@example.net","email_verified_at":null,"created_at":"2018-10-29 21:47:36","updated_at":"2018-10-29 21:47:36"}],"first_page_url":"http:\/\/localhost:8000\/api\/users?page=1","from":11,"last_page":10,"last_page_url":"http:\/\/localhost:8000\/api\/users?page=10","next_page_url":"http:\/\/localhost:8000\/api\/users?page=3","path":"http:\/\/localhost:8000\/api\/users","per_page":10,"prev_page_url":"http:\/\/localhost:8000\/api\/users?page=1","to":20,"total":100}

Laravel側は以上です。

React Native側の実装

で、ReactNative側。とりあえず標準で利用できるもので対応してみる。
以下の通り。

  • FlatListのonEndReached()で最後まで行ったかを確認。
  • onEndReachedThresholdはどこまで読み込まれたらendとするか(とりあえず0として最後までとする)
  • _fetchで読み込むが、newPageの+1ページを読み込み、表示配列のお尻に追加
  • 後は自動バインディングでデータが追加・表示される
  • last_page以下なら上記の処理を繰り返し、last_page以上では何もしない
  • 1000ページあっても読み込まれるので、ページ数に関係なく条件値(max_page)も設定

という感じ。

App.js
import React from 'react';
import { StyleSheet, Text, View, FlatList } from 'react-native';

export default class App extends React.Component {

  constructor(props) {
    super(props);
    this.state = {
      data: [],     //描画するデータ(追記されていく)
      page: 0,      //現在のページ
      last_page: 1, //last_pageで上書きされる
      max_page: 100 //ページ数に関係なく上限を設置(last_pageが150でも100で止まる)
    }
  }

  componentDidMount() {
    this._fetch();
  }

  _fetch = () => {

    if (this.state.page < this.state.last_page && this.state.page < this.state.max_page) {

      const newPage = this.state.page + 1;

      fetch(`http://localhost:8000/api/users?page=${newPage}`)
        .then(
          (response) => response.json()
        )
        .then((responseJson) => {
          this.setState({
            data: [...this.state.data, ...responseJson['data']], //今の配列に取得分を追加
            page: newPage,
            last_page: responseJson['last_page']
          })
          // console.log(responseJson['last_page'])
        })
        .catch((error) => {
          console.log(error);
        })

    }else{
      //何もしない
    }
  }

  render() {
    return (
      <View style={{ flex: 1, paddingVertical: 40 }}>
        <FlatList
          data={this.state.data}
          extraData={this.state.data}
          keyExtractor={(item) => item.id.toString()}
          renderItem={({ item }) => (
            <View style={{ padding: 10 }}>
              <Text
                style={{ fontSize: 24 }}
                onPress={() => alert(item.id)}
              >{item.id} {item.name} {item.email}</Text>
            </View>
          )}
          onEndReached={() => this._fetch()}
          onEndReachedThreshold={0}
        />
      </View>
    );
  }
}

ListFooterComponent={()=><ActivityIndicator size='large' animating />}を追加することで、ロード中、footerにspinnerが表示されます。

比較的簡単。便利。画面を引っ張って更新も簡単。

追記(2018年11月25日)

axiosつかったり、spinner入れたり、ListItem利用したりした改良版。

必要なライブラリのインストール。

npm install --save react-native-elements axios

以下実装。

App.js
import React from 'react';
import { StyleSheet, Text, View, FlatList, ActivityIndicator } from 'react-native';
import { ListItem } from 'react-native-elements';
import axios from 'axios';

export default class App extends React.Component {

  state = {
    data: [],
    page: 0,
    last_page: 1,
    max_page: 100,
    animating_status: true,
  }

  componentDidMount() {
    this.getDataFromLaravel();
  }

  render() {
    return (
      <View style={{ flex: 1, paddingVertical: 40 }}>
        <FlatList
          data={this.state.data}
          // extraData={this.state.data}
          keyExtractor={(item) => item.id.toString()}
          onEndReached={() => this.getDataFromLaravel()}
          onEndReachedThreshold={0}
          ListFooterComponent={() => <ActivityIndicator size='large' animating={this.state.animating_status} />}
          renderItem={({ item }) => (
            <ListItem
              key={item.id}
              title={item.id + ' ' + item.name}
              subtitle={item.email}
              hideChevron
              onPress={() => alert(item.id)}
              roundAvatar
              avatar={{uri: 'https://s3.amazonaws.com/uifaces/faces/twitter/ladylexy/128.jpg'}}
            />
          )}
        />
      </View>
    );
  }

  getDataFromLaravel = () => {

    if (this.state.page < this.state.last_page && this.state.page < this.state.max_page) {

      const newPage = this.state.page + 1;

      axios
        .get(`http://localhost:8000/api/users?page=${newPage}`)
        .then(res => {
          this.setState({
            data: [...this.state.data, ...res.data.data],
            page: newPage,
            last_page: res.data['last_page'],
          });
        })
        .catch(error => {
          console.log(error);
        })

    } else {
      this.setState({
        animating_status: false,
      });
      alert('これ以上読み込めません。');
    }
  }
}

なぜかAvatarの書き方は古い?表記じゃないと表示されませんでした(本家、サンプル通りじゃ動かない)。

15
14
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
15
14