[MERN] ページネーション


# Posts一覧のページネーション
##1. postsAPIを修正

・pageSize: 1ページのポスト数
・page: リクエストしたページ or 最初のページ
・limit(pageSize): PostデータはpageSize数まで表示
・skip(pageSize*(page-1)): 前のページのPostデータはスキップ

```routes/api/posts.js
//...
// @route  GET api/posts
// @desc   Get all posts
// @access Public
router.get('/', async (req, res) => {
+  const pageSize = 3;
+  const page = Number(req.query.pageNumber) || 1;

  const keyword = req.query.keyword
    ? {
        text: {
          $regex: req.query.keyword,
          $options: 'i',
        },
      }
    : {};

  try {
+    const count = await Post.countDocuments({ ...keyword });
    const posts = await Post.find({ ...keyword })
+      .limit(pageSize)
+      .skip(pageSize * (page - 1))
      .sort({ date: -1 });

+    res.json({ posts, page, pages: Math.ceil(count / pageSize) });

  } catch (err) {
    console.error(err.message);
    res.status(500).send('Server Error');
  }
});
//...
```

##2. getPostsアクションを修正

```actions/post.js
//...
// Get posts
+ export const getPosts = (keyword = '', pageNumber = '') => async (dispatch) => {
  try {
    const res = await axios.get(
+      `/api/posts?keyword=${keyword}&pageNumber=${pageNumber}`
    );
    dispatch({
      type: GET_POSTS,
      payload: res.data,
    });
  } catch (err) {
//...
```


##3. postsリデューサー

```reducers/post.js
//...
  switch (type) {
    case GET_POSTS:
      return {
        ...state,
+        posts: payload.posts,
+        pages: payload.pages,
+        page: payload.page,
        loading: false,
      };
    case GET_POST:
//...
```

##4. pageルーター設置

```components/routing/Routes.js
//...
      <Route exact path="/search/:keyword" component={Posts} />
+     <Route exact path="/page/:pageNumber" component={Posts} />
+     <Route
+       exact
+       path="/search/:keyword/page/:pageNumber"
+       component={Posts}
+     />
      <Route exact path="/posts" component={Posts} />
      <Route exact path="/posts/:id" component={Post} />
//...
```

##5. PostsページにPaginate追加

```components/posts/Posts.js
//...
+ import Paginate from '../layout/Paginate';
import { getPosts } from '../../actions/post';

const Posts = ({
  getPosts,
+ post: { posts, loading, pages, page },
  match,
  auth,
}) => {
  const keyword = match.params.keyword;
+  const pageNumber = match.params.pageNumber || 1;

  useEffect(() => {
+    getPosts(keyword, pageNumber);
+  }, [getPosts, keyword, pageNumber]);

  return loading ? (
    <Spinner />
  ) : (
    <Fragment>
      <h1 className="middle text-primary">RefNotes</h1>
      <p className="lead">
        <i className="fas fa-user" /> Welcome to the RefNotes
      </p>
      {auth.isAuthenticated === false ? null : <PostForm />}
      <div className="Posts">
        {posts.map((post) => (
          <PostItem key={post._id} post={post} />
        ))}
      </div>
+     <Paginate pages={pages} page={page} keyword={keyword ? keyword : ''} />
    </Fragment>
  );
};
//...
```

##6. Paginateコンポーネント作成

`react-router-bootstrap`のインストール
`$ npm install -S react-router-bootstrap`
https://www.npmjs.com/package/react-router-bootstrap 

```components/layout/Paginate.js
import React from 'react';
import { Pagination } from 'react-bootstrap';
import { LinkContainer } from 'react-router-bootstrap';

const Paginate = ({ pages, page, keyword = '' }) => {
  return (
    pages > 1 && (
      <Pagination>
        {[...Array(pages).keys()].map((x) => (
          <LinkContainer
            key={x + 1}
            to={keyword ? `/search/${keyword}/page/${x + 1}` : `/page/${x + 1}`}
          >
            <Pagination.Item active={x + 1 === page}>{x + 1}</Pagination.Item>
          </LinkContainer>
        ))}
      </Pagination>
    )
  );
};

export default Paginate;
```

# ユーザーページのページネーション

##1.