💡

react-routerの画面遷移時にAntDesign Tableのページネーションのページを制御する方法

2021/08/09に公開

この記事で扱う内容

  • react-routerの使い方(こちらの詳細に関しては別途記事作成予定)

  • AntDesignの簡単な使い方(主にTable)

  • ページネーションの制御

    • ページネーションのページ数
    • 初期位置の設定
    • 画面遷移時に現在の位置に戻す方法(例えばページネーションのページ数が3だった場合、ブラウザバックや更新などの画面遷移が発生した場合)
  • おまけでランダムに画像表示(Unsplash)

アプリの概要

現状、ページネーション3ページ目のモーダルへ移動するボタンを押して、そのモーダルを閉じるとページネーションが1ページ目にもどってしまいます。最終的にいくつかの方法でモーダルを閉じたときにページネーションが元の状態に戻るように制御してみたいと思います。

ルーティングの構成

import { useState } from 'react';

// BrowserRouter as Routerはエイリアス(別名)なので、BrowserRouterそのまま使用しても良し。
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';

import './App.css';

import TableContent from './Table';
import GetPhoto from './GetPhoto';
import GetFoodPhoto from './GetFoodPhoto';
import GetAnimalPhoto from './GetFoodPhoto copy';

import { Layout } from 'antd';
import 'antd/dist/antd.css';

const { Content } = Layout;

function App() {
 //modalの状態管理
  const [modalOpen, setModalOpen] = useState(false);

 //modalをただ開くだけの関数
  const showModal = (): void => {
    setModalOpen(true);
  };
  
  //modalを閉じるだけの関数
  const closeModal = (): void => {
    setModalOpen(false);
  };

  return (
    <>
      //三項演算子を利用して、modalOpenがtrueの時は<PaginationCheck>(確認用モーダル)のみが表示されるようにしている
      {modalOpen ? (
        <PaginationCheck closeModal={closeModal} />
      ) : (
        <Router>
	  //中央ぞろえにするstyleを当てているのみ。他にもいろいろできる。
          <Layout style={{ textAlign: 'center' }}>
	   //Contentは現状ほぼ機能していない。
            <Content>
              <h1>Hello React Router</h1>
              <Switch>
                <Route exact path='/'>
                  <Home />
                </Route>
                <Route path='/about'>
                  <About />
                </Route>
                <Route exact path='/contact'>
                  <Contact showModal={showModal} />
                </Route>
                <Route>
                  <NotFound />
                </Route>
              </Switch>
              <TableContent showModal={showModal} />
            </Content>
          </Layout>
        </Router>
      )}
    </>
  );
}

const Home = () => {
  return (
    <>
      <hr />
      <h2>Home</h2>
      <GetPhoto />
    </>
  );
};
const About = () => {
  return (
    <>
      <hr />
      <h2>About</h2>
      <GetFoodPhoto />
    </>
  );
};

const Contact = ({ showModal }: any) => {
  return (
    <>
      <hr />
      <h2>Contact</h2>
      <GetAnimalPhoto />
      <button onClick={showModal}>ページネーション確認用モーダルを開く</button>
    </>
  );
};
const NotFound = () => {
  return (
    <>
      <h2>Not Found Page</h2>;
    </>
  );
};
const PaginationCheck = ({ closeModal }: any) => {
  return (
    <>
      <h1>ページネーションの画面遷移を確認しましょう</h1>
      <button onClick={closeModal}>モーダルを閉じる</button>
    </>
  );
};

export default App;

react-routerに関しては必要最低限の機能でこんな感じでページ遷移が実装できますってくらいです。説明は割愛。

Tableの解説

import { Table, Layout } from 'antd';
import { Link } from 'react-router-dom';
const TableContent = ({ showModal }: any) => {
  const TableState = [
    {
      id: 1,
      key: 'a',
      title: 'test1',
      body: 'antdの使い方',
      link: <Link to='/'>Home</Link>,
    },
    {
      id: 2,
      key: 'b',
      title: 'test2',
      body: 'react-routerの使い方',
      link: <Link to='/about'>About</Link>,
    },
    {
      id: 3,
      key: 'c',
      title: 'test3',
      body: (
        <button onClick={showModal}>
          ページネーション確認用モーダルを開く
        </button>
      ),
      link: <Link to='/contact'>Contact</Link>,
    },
  ];

  const columns = [
    {
      title: 'ID',
      dataIndex: 'id',
      key: 'id',
    },
    {
      title: 'Title',
      dataIndex: 'title',
      key: 'title',
    },
    {
      title: 'Body',
      dataIndex: 'body',
      key: 'body',
    },
    {
      title: 'Link',
      dataIndex: 'link',
      key: 'link',
    },
  ];
  return (
    <Layout>
      <Table
        dataSource={TableState}
        columns={columns}
        pagination={{ pageSize: 1 }}
      />
    </Layout>
  );
};
export default TableContent;

公式からコードをそのまま持ってきて使うのですが、最初に触ったときは意味不明だったので簡略化して少し詳しく解説していこうかと思います。

上のtitleのような部分はcolumnsというオブジェクトで形成されています。

   {
      title: 'ID',
      dataIndex: 'id',
      key: 'id',
    }
  • title:表示したいタイトル
  • dataIndex: dataSourceオブジェクトのキーと紐づけしたい値
  • key: かぶらなければよし

そしてそのしたがdataSourseという配列を用意して、その中各オブジェクトのプロパティを参照してdataIndexの値とキーが同じ物があれば各テーブルに表示される仕組みになってます。

  {
      id: 1,
      key: 'a',
      title: 'test1',
      body: 'antdの使い方',
      link: <Link to='/'>Home</Link>,
    }

Tableコンポーネントを見てみるとこんな感じ。

      <Table
        dataSource={TableState}
        columns={columns}
        pagination={{ pageSize: 1 }} //この部分でpaginationのページ数を指定。何も指定しない場合は10になる
      />

この後paginationのパラメータとしてdeffaultCurrentと
TableのパラメータとしてonChangeが登場します

その他にも色んな使い方がありますので是非公式で。
英語なので理解できてない部分がほとんどです・・・

ページネーションの値を保持してページ遷移時に元に戻すには?

これが結構苦労した点でした。

routerの機能に注目していたのですが、そもそもページネーションはURL自体に操作を加えているわけではないんですよね。どうすればいいのやら?おそらくページネーション自体の制御はrouterでは無理だなと思い、antdの公式とにらめっこ。ちなみにページネーションのページ数の変え方もこの時発見しました。

そして、onChangeというモノを発見。ページネーションの値が変わると発動するみたいなことが書かれている気がしたのでちょっとやってみました。

   <Table
        dataSource={TableState}
        columns={columns}
        pagination={{ pageSize: 1, defaultCurrent: 1 }}
        onChange={(data) => {
          console.log(data);
        }}
      />

するとページネーションの値が変わるたびにその時の値がcurrentとして表示されることが判明。これを利用すれば実現できそうですね。

    <Layout>
      <Table
        dataSource={TableState}
        columns={columns}
        pagination={{
          pageSize: 1,
          defaultCurrent: defaultCurrent,
          onChange: (data) => {
            console.log(data);
          },
        }}
      />
    </Layout>

また、こちらのようにpaginarionパラメータを使用するとページネーションの値のみ出力できることが分かりましたのでこちらを使用します。

この値をどうやって保持するの?

次に直面するのはこの問題ですね。
ページ遷移が起こるので、この値をどうやって保持するのかという話になります。

3パターン程やったので軽く紹介

  • LocalStrageまたはSessionStrageに値を保存して、ページネーションが起こるたびに上書きする。

この方法だと違うURLに飛んだ時なども値が保存されることになりますので、そういった使い方をしたい場合は有効な手段だと思います。LocalStrageは半永久的、SessionStrageはブラウザを閉じるまで値の保持が有効となる、ブラウザが持つ記憶領域となります。パスワードなどはセキュリティ上絶対に保存してはいけないです。

  • history.push()でページネーションの値が変わるたびにその値をURLに?2などの形で書き込む。

こちらの方法でも問題なく動いていました。ページネーションが発生するたびにURLが
http://localhost:3000/?2 
のようになってしまうのでチョットキモイです。あと最初は?が無くページネーションで1に戻ってくると?1になるのもキモイです。あとその値を参照するときに?を取り除いてstringとして認識されているのでnumberに変換するという処理も発生しました。面倒ですね。

  • 最終的にたどり着いたのがこの方法。history.push()の第二引数にstateとして値を引き渡す。という方法。
//useHistory()を使用できるようにする。
const history = useHistory();
  const getCurrent = (page: number) => {
  //第一引数でこのURLに移動,第二引数でこの値をそのhistory内のstateとして維持。
    history.push('', { pageNumber: page });
  };

  //defaultCurrent(ページネーションの初期位置)
  let defaultCurrent: number = 1;
  //stateがtrueの場合(ページネーションの値の変更がありstateにプロパティがある場合。最初はundefind)に実行されるようにする。
  if (history.location.state) {
    const pageState: any = history.location.state;
    defaultCurrent = pageState.pageNumber;
  }
  return (
    <Layout>
      <Table
        dataSource={TableState}
        columns={columns}
        pagination={{
          pageSize: 1,
          defaultCurrent: defaultCurrent,
          onChange: getCurrent,
        }}
      />
    </Layout>
  );

アプリで確認

モーダルを閉じた時にページネーションの値が保持されていて3ページ目にもどってくることができました!

狙いの動きを実現することができましたね。

しかし、このまま使うには動画の途中でもわかるかもしれませんが少し不具合があります。

  • ページネーションの値が変わるたびに発動するhistory.push()の指定がHomeなのでHome以外の場所でページネーションの値を変えるたびにHomeに戻る。
  • Contactページにもページネーション確認用のモーダル(同じもの)があるがそちらでモーダルを開くとURLは/contactなのでstateを保持していないので1ページ目に戻る。

等の不具合があります。動的なルーティングを導入すれば回避できると思われるのですが、そちらはまた別途記事化したいと思っていますので今回はこれでおしまいにしたいと思います。historyは色々使い方があります。

おまけ

画像なのですがUnsplashというサイトからランダムで表示できるのでそれを活用しています。とても便利。

const GetAnimalPhoto = () => {
  return (
    <div>
      <img
        src='https://source.unsplash.com/random/?animal'
        alt='random animal imag'
        height={300}
      />
    </div>
  );
};

export default GetAnimalPhoto;

これでランダム取得

src='https://source.unsplash.com/random

こちらのようにするとランダム取得とジャンルでフィルタリングできるようです。(複数可)

src='https://source.unsplash.com/random/?animal

Discussion