React, Redux でパージネーションのスクロール表示を実装する。

忘れないように、メモ書き。

## API
SQL は、SQLite を使っていました。

```SQL
SELECT * FROM Hoge WHERE fuga="fuga1";
```

のような SQL文 を、LIMIT & OFFSET で 10件程度に分割する。

```SQL
SELECT * FROM Hoge WHERE fuga="fuga1" LIMIT 10 OFFSET 10;
```

これに URL を付与 (Railsなら routes.rb、Djangoなら urls.py の url patterns、Express なら app.get('url', function(req, res) => ){} ) して、`http://hoge-api.com/hoges?offet=num` から hoge の num の offset からのデータを取得できるようにする。

## Redux
Redux Thunk を使ってかく。

```javascript:src/actions/index.js
export const CONCAT_HOGE_SUCCESS = "CONCAT_HOGE_SUCCESS"
export const CONCAT_HOGE_FAIL = "CONCAT_HOGE_FAIL"

export const concatHoge = (offset) => {
  return (dispatch) => {
    axis({
      url: "http://hoge-api.com/hoges?offet=" + offset,
      method: "POST"
    }).then((response) => {
      let response_status = response["data"]["status"]
      if (response_status == "ok"){
        let response_data = response["data"]["hoge"]
        dispatch(concatHogeSuccess(response_data))
      } else {
        dispatch(concatHogeFail())
      }
    })
  }
}

const concatHogeSuccess = (data) => {
  return{
    type: CONCAT_HOGE_SUCCESS,
    data: data,
  }
}

const concatHogeFail = () => {
  return {
    type: CONCAT_HOGE_FAIL,
  }
}

```

```javascript:src/reducers/hoge.js
import {CONCAT_HOGE_SUCCESS, CONCAT_HOGE_FAIL} from "../actions";

export const hoge = (state=[], action) => {
  switch(action.type){
  // ... 省略
    case CONCAT_HOGE_SUCCESS:
      return [...state, ...action.data]
    case CONCAT_HOGE_FAIL:
      return state
  }
}
```

これで、下のようなReactのコードで、ボタンを押せば、パージネーション の続きが見れるようになる。

```javascript:src/hoge.js
import {useState, useRef} from "react"
import {useDispatch, useSelector} from "react-redux"
// ... 省略
const hoge = () => {
  const dispatch = useDispatch()
  const hoge = useSelector(state => state.hoge)
  // setTimeout 用 の state
  const [offset, setOffset] = useState(2) /* API の URL に合わせる */
  const offsetRef = useRef(offset)
  offsetRef.current = offset
  // ... 省略
  const onClickHogeOffset = () => {
    dispatch(offset)
    let next_offset = offset + 1
    setOffset(next_offset)
  }
  return {
   <> 
     {hoge.map((item) => {
        // ... 省略
      })}
      <div className="pagination-next-area">
        <button onClick={() => onClickHogeOffset()}>
          もっと見る
        </button>
      </div>
    </>
  }
}
```

## React

javascript の event の [scroll](https://developer.mozilla.org/ja/docs/Web/API/Document/scroll_event) を使う。
上の Reactのファイル に、まず useEffect で 初期のスクロール位置を取得し、window に onScrollPagination イベントを追加します。

```javascript:src/hoge.js
import {useState, useRef, useEffect} from "react"
// ... 省略
const hoge = () => {
// ... 省略
  const [paginationPos, setPaginationPos] = useState(0)
  const paginationPosRef = useRef(paginationPos)
  paginationPosRef.current = paginationPos
// ... 省略
  useEffect(() => {
    window.setTimeout(initializePagination, 1000) /* 描画が遅れる問題 */
  })
// ... 省略
  const initializePagination = () => {
    var pagination_area = document.getElementsByClassName("pagination-next-area")[0]
    setPaginationPos(pagination_area.offsetTop)
    window.addEventListener("scroll", onScrollPagination)
  }
  const onScrollPagination = () => {
    // ここに scroll イベントを実装する
  }
}
```

window の位置が、上記の paginationPos を越した場合に、上記の `dispatch(concatHoge(offset))` を含んだ `onClickHogeOffset` を発火させる。

```javascript:src/hoge.js
import concatHoge from "./actions"
// ... 省略
  const onScrollPagination = () => {
    if (window.scrollY + window.innerHeight > paginationPosRef.current ) {
      onClickHogeOffset()
      var pagination_area = document.getElementsByClassName("pagination-next-area")[0]
      pagination_area.removeEvent("scroll", onScrollPagination)
      // PaginationPos を更新する
      setPaginationPos(pagination_area.offsetTop)
      window.addEventListener("scroll", onScrollPagination)
    }
  }

```

以上でした。