はじめに
今まで作ったアプリケーションに今回は登録されているデータを条件によって検索できる機能を実装していきました。
今回作ったアプリケーション
一つの画面の中に条件フォームを作り、絞りこみボタンを押すとその条件に合致した情報を画面上に表示する機能です。
例えば以下の画像では「好きな住居形態」という項目で「森」を選んだ結果、合致するゴリラとhatakeyamaが画面に表示されました。
前回のページネーションがちゃんと作動しているのでページネーション番号もちゃんと表示されています。
また絞る条件は一項目ではなく複数項目選ぶことができます。
今回使用するコンポーネント
条件検索のロジックを持ったUserInfoFilterコンポーネントと条件検索で帰ってきた値を格納するUserInfoコンポーネントを今回使っていきます。
UserInfoFilter.tsx(条件検索するコンポーネント)
import * as React from 'react'
import { useContext } from 'react'
import { Store } from './App'
import axios from 'axios'
import Form from './Form'
import { displayPage } from './UserInfo'
const UserInfoFilter = (props: any) => {
const { dispatch } = useContext(Store)
//Formコンポーネントが持っているusers情報をコールバック関数で受け取っている
const searchData = async (name: string, gender: string, age: number, address: string, password: string, email: string, postNumber: string, likeResidenceType: string, position: string, language: string) => {
const nameParameter = name ? 'users.name=' + name + '&' : ''
const genderParameter = gender ? 'users.gender=' + gender + '&' : ''
const ageParameter = age ? 'users.age=' + age + '&' : ''
const addressParameter = address ? 'users.address=' + address + '&' : ''
const passwordParameter = password ? 'users.password=' + password + '&' : ''
const emailParameter = email ? 'users.email=' + email + '&' : ''
const postNumberParameter = postNumber ? 'users.post_number=' + postNumber + '&' : ''
const likeResidenceTypeParameter = likeResidenceType ? 'users.like_residence_type=' + likeResidenceType + '&' : ''
const positionParameter = position ? 'users.position=' + position + '&' : ''
const languageParameter = language ? 'users.language=' + language + '&' : ''
// ajaxパラメータの中身
const searchParams = nameParameter + genderParameter + ageParameter + addressParameter + passwordParameter + emailParameter + postNumberParameter + likeResidenceTypeParameter + positionParameter + languageParameter
// 親に渡すparams
const passParams = {
name,
gender,
age,
address,
password,
email,
postNumber,
likeResidenceType,
position,
language
}
try {
dispatch({ type: 'SHOW_INDICATOR' })
axios.defaults.headers['X-Requested-With'] = 'XMLHttpRequest'
// pageindexの作成
await axios.get(
'/d/users?' + searchParams + '_pagination=1,4&l=' + displayPage
)
.then(async (res) => {
//条件検索時の総取得件数
console.log(res.data.feed.subtitle)
props.click(passParams, res.data.feed.subtitle)
})
.then(() => {
dispatch({ type: 'HIDE_INDICATOR' })
})
} catch (e) {
alert('error:' + e)
dispatch({ type: 'HIDE_INDICATOR' })
}
}
return (
<>
<h3>絞り込み検索</h3>
<Form click={searchData} submitName={'絞り込む'} />
</>
)
}
export default UserInfoFilter
このコンポーネントの流れは
return (
<>
<h3>絞り込み検索</h3>
<Form click={searchData} submitName={'絞り込む'} />
</>
)
フォームコンポーネントにpropsでsearchDataメソッドとFormコンポーネントの中のボタンに絞りこみという名前を渡しています。
ちなみにこの部分です。
この絞りこみボタンを押すと、入力されたデータを親コンポーネント(UserInfo.tsx)に渡します。
入力された後にsearchDataメソッドが発火します。
const searchData = async (name: string, gender: string, age: number, address: string, password: string, email: string, postNumber: string, likeResidenceType: string, position: string, language: string) => {
const nameParameter = name ? 'users.name=' + name + '&' : ''
const genderParameter = gender ? 'users.gender=' + gender + '&' : ''
const ageParameter = age ? 'users.age=' + age + '&' : ''
const addressParameter = address ? 'users.address=' + address + '&' : ''
const passwordParameter = password ? 'users.password=' + password + '&' : ''
const emailParameter = email ? 'users.email=' + email + '&' : ''
const postNumberParameter = postNumber ? 'users.post_number=' + postNumber + '&' : ''
const likeResidenceTypeParameter = likeResidenceType ? 'users.like_residence_type=' + likeResidenceType + '&' : ''
const positionParameter = position ? 'users.position=' + position + '&' : ''
const languageParameter = language ? 'users.language=' + language + '&' : ''
// ajaxパラメータの中身
const searchParams = nameParameter + genderParameter + ageParameter + addressParameter + passwordParameter + emailParameter + postNumberParameter + likeResidenceTypeParameter + positionParameter + languageParameter
// 親に渡すparams
const passParams = {
name,
gender,
age,
address,
password,
email,
postNumber,
likeResidenceType,
position,
language
}
try {
dispatch({ type: 'SHOW_INDICATOR' })
axios.defaults.headers['X-Requested-With'] = 'XMLHttpRequest'
// pageindexの作成
await axios.get(
'/d/users?' + searchParams + '_pagination=1,4&l=' + displayPage
)
.then(async (res) => {
//条件検索時の総取得件数
console.log(res.data.feed.subtitle)
props.click(passParams, res.data.feed.subtitle)
})
.then(() => {
dispatch({ type: 'HIDE_INDICATOR' })
})
} catch (e) {
alert('error:' + e)
dispatch({ type: 'HIDE_INDICATOR' })
}
}
const nameParameter = name ? 'users.name=' + name + '&' : ''
三項演算子でFormから渡ってきたデータ項目があればページインデックスを作成するための加工をします。なければ空文字を返します。
条件検索を使ったインデックスを作成するためにはajax通信で以下のパラメーターを使います。
'/d/{エンドポイント名}?条件&_pagination={開始ページ、終了ページ}&l={1ページの表示件数}'
// pageindexの作成
await axios.get(
'/d/users?' + searchParams + '_pagination=1,4&l=' + displayPage
)
.then(async (res) => {
//条件検索時の総取得件数
props.click(passParams, res.data.feed.subtitle)
})
この部分ですね。
また、
props.click(passParams,res.data.feed.subtitle)
の部分で親に項目のデータと総件数(res.data.feed.subtitleで総件数が帰ってきます。)を渡しています。
UserInfo.tsx(UserInfoFilterで貼ったインデックスをもとにデータをとってくるコンポーネント)
import * as React from 'react'
import { useState, useEffect, useContext, useRef } from 'react'
import axios from 'axios'
import UserList from './UserList'
import { Store } from './App'
import Pagination from './Pagination'
import UserInfoFilter from './UserInfoFilter'
interface SearchConditions {
name?: string
gender?: '男' | '女' | ''
age?: number
address?: string
password?: string
email?: string
postNumber?: string
likeResidenceType?: string
position?: string
language?: string
}
// 1ページに表示させる件数
export const displayPage = 5
const UserInfo = () => {
// apiを叩いてgetしたデータをusersに格納する
const [users, setUsers] = useState([])
// 検索条件(UserInfoFilterから渡ってくる)
const [searchConditions, setSearchConditions] = useState<SearchConditions>()
// 総ページ数
const [sumPageNumber, setSumPageNumber] = useState(0)
// 現在見ているページネーション
const [currentPage, setCurrentPage] = useState(1)
const { dispatch } = useContext(Store)
// コンポーネントマウント後に以下のページインデックスを作成する関数が実行される
// 初期描画の実行
useEffect(() => {
getTotalFeedNumber()
console.log('useEffect:getTotalFeedNumber')
}, [])
// 初期描画後
// 最初にページインデックスを作成終了後、handlePaginateで1ページを指定している
const mounted = useRef(false)
useEffect(() => {
if (mounted.current) {
if (sumPageNumber === 0) {
setUsers([])
return
}
handlePaginate(1)
console.log('useEffect:mounted.current=true')
} else {
mounted.current = true
console.log('useEffect:mounted.current=false')
}
}, [sumPageNumber])
//ページの取得処理
let retryCount = 0
// この処理をgetTotalFeedNumberを処理したときに実行したい
//page番号を使ってAPIを叩く処理
const handlePaginate = async (page: number) => {
const nameParameter = searchConditions?.name ? 'users.name=' + searchConditions.name + '&' : ''
const genderParameter = searchConditions?.gender ? 'users.gender=' + searchConditions.gender + '&' : ''
const ageParameter = searchConditions?.age ? 'users.age=' + searchConditions.age + '&' : ''
const addressParameter = searchConditions?.address ? 'users.address=' + searchConditions.address + '&' : ''
const passwordParameter = searchConditions?.password ? 'users.password=' + searchConditions.password + '&' : ''
const emailParameter = searchConditions?.email ? 'users.email=' + searchConditions.email + '&' : ''
const postNumberParameter = searchConditions?.postNumber ? 'users.post_number=' + searchConditions.postNumber + '&' : ''
const likeResidenceTypeParameter = searchConditions?.likeResidenceType ? 'users.like_residence_type=' + searchConditions.likeResidenceType + '&' : ''
const positionParameter = searchConditions?.position ? 'users.position=' + searchConditions.position + '&' : ''
const languageParameter = searchConditions?.language ? 'users.language=' + searchConditions.language + '&' : ''
// リトライ回数
const LIMIT_RETRY_COUNT = 10
const searchParams = nameParameter + genderParameter + ageParameter + addressParameter + passwordParameter + emailParameter + postNumberParameter + likeResidenceTypeParameter + positionParameter + languageParameter
try {
console.log('handlePaginateが作動しました' + page)
console.log(searchParams)
dispatch({ type: 'SHOW_INDICATOR' })
axios.defaults.headers['X-Requested-With'] = 'XMLHttpRequest'
if (searchConditions) {
await axios.get(
'/d/users?' + searchParams + 'n=' + page + '&l=' + displayPage
).then((res) => {
if (res && res.data && res.data.length) {
setUsers(res.data)
}
setCurrentPage(page)
}).then(() => {
retryCount = 0
dispatch({ type: 'HIDE_INDICATOR' })
})
} else {
await axios.get(`/d/users?n=${page}&l=${displayPage}`).then((res) => {
if (res && res.data && res.data.length) {
setUsers(res.data)
}
setCurrentPage(page)
}).then(() => {
retryCount = 0
dispatch({ type: 'HIDE_INDICATOR' })
})
}
} catch (e) {
if (e.response.data.feed.title === 'This process is still in progress. Please wait.') {
retryCount++
console.log(retryCount)
if (retryCount < LIMIT_RETRY_COUNT) {
handlePaginate(page)
} else {
dispatch({ type: 'HIDE_INDICATOR' })
alert('error:' + e)
alert('Process error')
}
}
if (e.response.data.feed.title === 'Please make a pagination index in advance.') {
retryCount++
console.log(retryCount)
if (retryCount < LIMIT_RETRY_COUNT) {
handlePaginate(page)
} else {
dispatch({ type: 'HIDE_INDICATOR' })
alert('error:' + e)
alert('Not create pagination index')
}
}
}
}
// paginationIndexを作成する処理
const getTotalFeedNumber = async () => {
try {
dispatch({ type: 'SHOW_INDICATOR' })
axios.defaults.headers['X-Requested-With'] = 'XMLHttpRequest'
await axios.get(`/d/users?_pagination=1,4&l=${displayPage}`).then((res) => {
setSumPageNumber(res.data.feed.subtitle)
console.log(res)
}).then(() => {
dispatch({ type: 'HIDE_INDICATOR' })
})
} catch (e) {
dispatch({ type: 'HIDE_INDICATOR' })
alert('error:' + e)
}
}
const searchedPaginate = async (passedParams: SearchConditions, passedSumPageNumber: number) => {
//子から渡される検索条件
await setSearchConditions(passedParams)
//子から渡される条件検索後のページ総件数
await setSumPageNumber(passedSumPageNumber)
}
return (
<>
<UserInfoFilter click={searchedPaginate} />
<h3>情報一覧</h3>
<p>総件数:<span style={{ color: 'blue' }}>{sumPageNumber}</span>件</p>
<p>現在<span style={{ color: 'blue' }}>{currentPage}</span>ページ目</p>
<UserList info={users} />
<Pagination sum={sumPageNumber} per={displayPage} onChange={e => handlePaginate(e.page)} />
<button onClick={(e) => { e.preventDefault(), console.log(sumPageNumber) }}>sumPageNumberを調べる</button>
</>
)
}
export default UserInfo
UserInfoコンポーネントの子コンポーネントであるUserInfoFilter.tsxから渡される情報を引数として受け取り、setSearchConditions
で値を格納します。
const searchedPaginate = async (passedParams: SearchConditions, passedSumPageNumber: number) => {
//子から渡される検索条件
await setSearchConditions(passedParams)
//子から渡される条件検索後のページ総件数
await setSumPageNumber(passedSumPageNumber)
}
// 検索条件(UserInfoFilterから渡ってくる)
const [searchConditions, setSearchConditions] = useState<SearchConditions>(
このsearchConditionsというものがなんのために必要なのかというと、ページネーションをする際に必要になってきます。
また,setSumPageNumberすることによってSumPageNumberの値が変わり、SumPageNumberを監視しているuseEffectによってPaginateメソッドが作動します。
handlePaginateメソッドの中で
if (searchConditions) {
await axios.get(
'/d/users?' + searchParams + 'n=' + page + '&l=' + displayPage)
} else {
await axios.get(`/d/users?n=${page}&l=${displayPage}`)
}
という部分があり、ページネーションをする際に毎回searchConditionsに値があれば条件検索をされたページネーションする流れになっています。
なければ通常通りの条件なしでのページネーションします。
まとめ
①UserInfoFilterでインデックスを貼る
②検索条件があればUserInfoIndexで貼ったインデックスを参照してページネーションする
といった流れで条件検索をすることができました。