在哪里实施 SWRs 分页来管理 url 中的分页?
Where to implement SWRs pagination for managing pagination in url?
我一直在尝试解决这个 NextJS 应用程序没有在后端处理任何分页的问题,所以我的想法是将它传递给 url 中的查询参数,所以 localhost:3000/patients?page=
.
我接近这种方法:
import React, { useEffect } from 'react'
import PatientsTable from 'components/patients/PatientsTable'
import useSWRWithToken from 'hooks/useSWRWithToken'
import Feedback from 'components/feedback'
import { useRouter } from 'next/router'
function Patients(props) {
const { data: patientsList, error: patientsListError } =
useSWRWithToken('/patients')
const router = useRouter()
const { page, rowsPerPage, onPageChange, query } = props
useEffect(() => {
const { pg } = props
const nextPage = parseInt(pg)
if (page !== nextPage) {
router.replace({
query: {
...router.query,
pg: page,
},
})
}
}, [page, query, router, router.replace])
return (
<>
<Feedback />
<PatientsTable
patientsList={patientsList}
patientsListError={patientsListError}
/>
</>
)
}
Patients.layout = 'fullScreen'
Patients.auth = true
export default Patients
但是转到下一页和上一页的事件处理程序停止工作:
import React from 'react'
import { IconButton } from '@mui/material'
import KeyboardArrowLeft from '@mui/icons-material/KeyboardArrowLeft'
import KeyboardArrowRight from '@mui/icons-material/KeyboardArrowRight'
import { styled, useTheme } from '@mui/system'
const Root = styled('div')(({ theme }) => ({
flexShrink: 0,
marginLeft: theme.spacing(2.5),
}))
const TablePaginationActions = (props) => {
const theme = useTheme()
const { count, page, rowsPerPage, onPageChange } = props
const handleFirstPageButtonClick = (event) => {
onPageChange(event, 0)
}
const handleBackButtonClick = (event) => {
onPageChange(event, page - 1)
}
const handleNextButtonClick = (event) => {
onPageChange(event, page + 1)
}
const handleLastPageButtonClick = (event) => {
onPageChange(event, Math.max(0, Math.ceil(count / rowsPerPage) - 1))
}
return (
<Root>
<IconButton
onClick={handleBackButtonClick}
disabled={page === 0}
aria-label="previous page"
data-cy={'table-pagination-actions-icon-button-prev'}
>
{theme.direction === 'rtl' ? (
<KeyboardArrowRight />
) : (
<KeyboardArrowLeft />
)}
</IconButton>
<IconButton
onClick={handleNextButtonClick}
disabled={page >= Math.ceil(count / rowsPerPage) - 1}
aria-label="next page"
data-cy={'table-pagination-actions-icon-button-next'}
>
{theme.direction === 'rtl' ? (
<KeyboardArrowLeft />
) : (
<KeyboardArrowRight />
)}
</IconButton>
</Root>
)
}
export default TablePaginationActions
我想说这是因为我的 useEffect
钩子被 useSWR
钩子以某种方式破坏了,但我无法证明这一点。
我正在尝试执行以下操作:
https://swr.vercel.app/docs/pagination
这是useSWRWithToken:
import useSWR from 'swr'
import fetchWithToken from 'libs/fetchWithToken'
import { useAppContext } from 'context'
function useSWRWithToken(endpoint: any, dependency: Boolean = true) {
const { state } = useAppContext()
const {
token: { accessToken },
} = state
const { data, error, mutate } = useSWR<any>(
// Make sure there is an accessToken and null does not appear in the uri
accessToken && endpoint.indexOf(null) === -1 && dependency
? endpoint
: null,
(url: string, params: object = null) =>
fetchWithToken(url, accessToken, params)
)
return {
data,
isLoading: !error && !data,
error,
mutate,
}
}
export default useSWRWithToken
这是患者 Table 组件:
import React, {
useEffect,
useState,
useMemo,
useCallback,
ReactChild,
} from 'react'
import { Paper } from '@mui/material'
import { LinearLoader } from 'components/loaders'
import { useRouter } from 'next/router'
import GeneralTable from 'components/table/GeneralTable'
import { parseInt, isNil } from 'lodash'
import MultiSelectChip, {
ChipCommonFilter,
} from 'components/forms/MultiSelectChip'
import usePermissions from 'hooks/usePermissions'
import { differenceInDays } from 'date-fns'
import { useAppContext } from 'context'
import { PatientsTableColumns } from 'components/table/patients/PatientsTableColumns'
import ProviderSelect from 'components/patients/ProviderSelect'
// TODO: declare interface types
interface IPatientList {
patientsList: any
patientsListError?: any
}
function PatientsTable({
patientsList = null,
patientsListError,
}: IPatientList) {
const router = useRouter()
const { state, dispatch } = useAppContext()
const { controlLevelFilters, selectedProviderFilter } = state.patientSearch
const [providerList, setProviderList] = useState<Array<string>>([])
const [dataParsed, setDataParsed] = useState([])
const [controlFilterOptions, setControlFilterOptions] = useState(['all'])
const [scroll, setScroll] = useState(false)
const { permissionPublicUserId }: { permissionPublicUserId: boolean } =
usePermissions()
const setControlLevelFilters = (value: string[]) => {
dispatch({
type: 'SET_CONTROL_LEVEL_FILTERS',
payload: {
controlLevelFilters: value,
},
})
}
const setSelectedProviderFilter = (provider: string) => {
console.log('provider: ', provider)
dispatch({
type: 'SET_SELECTED_PROVIDER_FILTER',
payload: {
selectedProviderFilter: provider,
},
})
}
const setSortState = (memoColumn: string, memoDirection: string) => {
dispatch({
type: 'SET_PATIENT_TABLE_SORT',
payload: {
columnName: memoColumn,
direction: !memoDirection ? 'asc' : 'desc',
},
})
}
const handleChangeControlLevelFilter = (
event: any,
child: ReactChild,
deleteValue: string
) => {
ChipCommonFilter(
event,
child,
deleteValue,
setControlLevelFilters,
controlLevelFilters
)
}
useEffect(() => {
dispatch({
type: 'SET_PAGE_HEADING',
payload: {
pageHeading1: 'Patients',
pageHeading2: `${
patientsList?.length ? `(${patientsList.length})` : ''
}`,
},
})
}, [patientsList])
useEffect(() => {
// Build up a list of patient objects which our table can traverse
const dataParsed = patientsList?.map((row) => {
!isNil(row.doctorDto) &&
setProviderList((previousProviderList) => [
...previousProviderList,
`${row.doctorDto.firstName} ${row.doctorDto.lastName}`,
])
const reportedDate = row.scalarReports.filter(
(obj) => obj.name === 'lastUseInDays'
)[0]?.reportedOn
const diffDate: number = Math.abs(
differenceInDays(new Date(reportedDate), new Date())
)
const lastUsed: string | number =
diffDate > 7
? diffDate
: diffDate === 0 && !isNil(reportedDate)
? 'Today'
: isNil(reportedDate)
? '--'
: diffDate
return {
pui: row.pui,
provider: !isNil(row.doctorDto)
? `${row.doctorDto.firstName} ${row.doctorDto.lastName}`
: '',
name: `${row.firstName} ${row.lastName}`,
dob: row.dob,
asthmaControl: row.scalarReports.filter(
(obj) => obj.name === 'asthmaControl'
)[0]?.value,
lastUsed,
fev1Baseline: row.scalarReports.filter(
(obj) => obj.name === 'fevBaseLine'
)[0]?.value,
}
})
setDataParsed(dataParsed)
}, [patientsList])
useEffect(() => {
window.addEventListener('scroll', () => {
setScroll(window.scrollY > 50)
})
}, [])
useEffect(() => {
if (dataParsed) {
setControlFilterOptions([
'all',
...(dataParsed.find((patient) => patient.asthmaControl === 'good')
? ['good']
: []),
...(dataParsed.find((patient) => patient.asthmaControl === 'poor')
? ['poor']
: []),
...(dataParsed.find((patient) => patient.asthmaControl === 'veryPoor')
? ['veryPoor']
: []),
])
}
}, [dataParsed])
const handleSelectProvider = (provider) => setSelectedProviderFilter(provider)
const isToday = (val) => val === 'Today' || val === 'today'
const isInactive = (val) => val === 'Inactive' || val === 'inactive'
const isDash = (val) => isNil(val) || val === '--'
const isAsthmaControl = (val) => {
const _val = val?.toString().toLowerCase()
let result: Number | boolean = false
switch (_val) {
case 'verypoor':
result = 1
break
case 'poor':
result = 2
break
case 'good':
result = 3
break
default:
result = false
}
return result
}
const CustomSortBy = useCallback(
(rowA, rowB, colId, direction) => {
const convertValue = (val) => {
if (isToday(val)) return 0 //Today != 1
if (isInactive(val)) return direction === false ? -2 : 29999
if (isDash(val)) return direction === false ? -3 : 30000
const acResult = isAsthmaControl(val) //so we don't call it twice
if (acResult) return acResult
return parseInt(val)
}
const v1 = convertValue(rowA.values[colId])
const v2 = convertValue(rowB.values[colId])
return v1 >= v2 ? 1 : -1 // Direction var doesn't matter.
},
[patientsList, isToday, isInactive, isDash]
)
const columns = useMemo(() => PatientsTableColumns(CustomSortBy), [])
const rowLinkOnClick = (patient) => {
router.push(`/patients/${patient.pui}`)
}
// TODO put better error here
if (patientsListError) return <div>Failed to load patients list</div>
if (!patientsList) return <LinearLoader />
return (
<Paper elevation={0} square>
{patientsList && dataParsed && (
<GeneralTable
retainSortState={true}
sortStateRecorder={setSortState}
columns={columns}
data={dataParsed}
checkRows={false}
rowLinkOnClick={rowLinkOnClick}
filters={[
{
column: 'asthmaControl',
options: controlLevelFilters,
},
{
column: 'provider',
options: selectedProviderFilter,
},
]}
initialState={{
sortBy: [
{
id: state.patientTableSort.columnName,
desc: state.patientTableSort.direction === 'desc',
},
],
hiddenColumns: false && !permissionPublicUserId ? [] : ['pui'], //For now, intentionally always hide
}}
leftContent={
<div style={{ display: 'flex' }}>
<MultiSelectChip
labelText="Asthma Control"
options={controlFilterOptions}
selectedValues={controlLevelFilters}
handleChange={handleChangeControlLevelFilter}
/>
<ProviderSelect
providers={Array.from(new Set(providerList))}
providerFilter={selectedProviderFilter}
handleChange={handleSelectProvider}
/>
</div>
}
/>
)}
</Paper>
)
}
export default PatientsTable
我还应该提到全局状态进入 appReducer
像这样:
import combineReducers from 'react-combine-reducers'
import { ReactElement } from 'react'
enum ActionName {
SET_PAGE_HEADING = 'SET_PAGE_HEADING',
SET_TIMER_RUNNING = 'SET_TIMER_RUNNING',
SET_PATIENT_DATA = 'SET_PATIENT_DATA',
SET_CHART_PERIOD = 'SET_CHART_PERIOD',
SET_TOKEN = 'SET_TOKEN',
}
enum ChartPeriod {
WEEK = 'week',
THREE_MONTH = '1m',
ONE_MONTH = 'week',
}
type Action = {
type: string
payload: any
}
// Types for global App State
type PageHeadingState = {
pageHeading1: string
pageHeading2?: string
component?: ReactElement
}
// TODO: fill this in or reference existing type
// types like this need to be centralized
type Patient = object
type PatientDataState = [
{
[key: string]: Patient
}
]
type PatientSearch = {
controlLevelFilters: string[]
selectedProviderFilter: string[]
}
type TimerState = {
running: boolean
visitId?: string | null
stopTimer?: () => void
}
type AppState = {
pageHeading: PageHeadingState
timer: TimerState
// TODO: flesh out what the shape of this data is and type it
// once the swagger definition is complete (state: PatientDataState)
patientData: PatientDataState
chartPeriod: ChartPeriod
patientSearch: PatientSearch
patientTableSort: PatientTableSort
token: object
}
// A reducer type to aggregate (n) reducers
type AppStateReducer = (state: AppState, action: Action) => AppState
// Initial State for the app
const initialPageHeading = {
pageHeading1: '',
pageHeading2: '',
}
const initialTimer = {
running: false,
}
const initialChartPeriod = ChartPeriod.WEEK
const initialPatientData: PatientDataState = [{}]
const initialPatientSearch: PatientSearch = {
controlLevelFilters: ['all'],
selectedProviderFilter: ['All Providers'],
}
type PatientTableSort = { columnName: string; direction: 'asc' | 'desc' }
const initialPatientTableSort: PatientTableSort = {
columnName: 'asthmaControl',
direction: 'desc',
}
// Perhaps can make this more explicit with STOP_PATIENT_CARE_TIMER
// and STOP_PATIENT_CARE_TIMER action cases
// I have kept the CRUD to a minimum for this first POC
const timerReducer = (state: TimerState, action: Action) => {
switch (action.type) {
case ActionName.SET_TIMER_RUNNING:
return { ...state, ...action.payload }
// case ActionName.STOP_PATIENT_CARE_TIMER:
// return { running: false }
default:
return state
}
}
const pageHeadingReducer = (state: PageHeadingState, action: Action) => {
switch (action.type) {
case ActionName.SET_PAGE_HEADING: {
return {
...state,
...action.payload,
}
}
default:
return state
}
}
// TODO: flesh out what the shape of this data is and type it
// once the swagger definition is complete (state: PatientDataState)
const patientDataReducer = (state: PatientDataState, action) => {
switch (action.type) {
case ActionName.SET_PATIENT_DATA: {
return action.payload
}
default:
return state
}
}
const chartPeriodReducer = (state: ChartPeriod, action: Action) => {
switch (action.type) {
case ActionName.SET_CHART_PERIOD: {
return action.payload
}
default:
return state
}
}
const patientSearchReducer = (state: PatientSearch, action: Action) => {
switch (action.type) {
case 'SET_SELECTED_PROVIDER_FILTER': {
return { ...state, ...action.payload }
}
case 'SET_CONTROL_LEVEL_FILTERS': {
return { ...state, ...action.payload }
}
default:
return state
}
}
const patientTableSortReducer = (state: PatientTableSort, action: Action) => {
switch (action.type) {
case 'SET_PATIENT_TABLE_SORT': {
return { ...state, ...action.payload }
}
case 'CLEAR_PATIENT_TABLE_SORT': {
const update = { columnName: '', direction: 'asc' }
return { ...state, ...update }
}
default:
return state
}
}
const tokenReducer = (state: object, action: Action) => {
switch (action.type) {
case 'SET_TOKEN': {
return action.payload
}
default:
return state
}
}
// This is exposed for use in AppContext.tsx so bootstrap our store/state
export const [AppReducer, initialState] = combineReducers<AppStateReducer>({
pageHeading: [pageHeadingReducer, initialPageHeading],
timer: [timerReducer, initialTimer],
patientData: [patientDataReducer, initialPatientData],
chartPeriod: [chartPeriodReducer, initialChartPeriod],
patientSearch: [patientSearchReducer, initialPatientSearch],
patientTableSort: [patientTableSortReducer, initialPatientTableSort],
token: [tokenReducer, {}],
})
由于你的全局缓存应该考虑当前页面来管理你的数据,这个数据应该出现在键中,然后你必须像useSWRWithToken(`/patients/?page${currentPage}`)
一样通过你的钩子发送它然后每次当前页面更改 SWR 将触发新的提取。
我建议不要使用字符串作为键,而是使用包含所有数据的数组,例如
const fetcher = async (key: string, id: string, pagination: number) => {
//He in your fetch you'll receive the key ordered by the array
fetchWithToken(key, accessToken, [id, page: pagination])
}
const useSWRWithToken = (key, id, pagination) = {
const { data, error, isValidating } = useSWR(
[`patients/${id}`, id, pagination], fetcher, options
)
}
我一直在尝试解决这个 NextJS 应用程序没有在后端处理任何分页的问题,所以我的想法是将它传递给 url 中的查询参数,所以 localhost:3000/patients?page=
.
我接近这种方法:
import React, { useEffect } from 'react'
import PatientsTable from 'components/patients/PatientsTable'
import useSWRWithToken from 'hooks/useSWRWithToken'
import Feedback from 'components/feedback'
import { useRouter } from 'next/router'
function Patients(props) {
const { data: patientsList, error: patientsListError } =
useSWRWithToken('/patients')
const router = useRouter()
const { page, rowsPerPage, onPageChange, query } = props
useEffect(() => {
const { pg } = props
const nextPage = parseInt(pg)
if (page !== nextPage) {
router.replace({
query: {
...router.query,
pg: page,
},
})
}
}, [page, query, router, router.replace])
return (
<>
<Feedback />
<PatientsTable
patientsList={patientsList}
patientsListError={patientsListError}
/>
</>
)
}
Patients.layout = 'fullScreen'
Patients.auth = true
export default Patients
但是转到下一页和上一页的事件处理程序停止工作:
import React from 'react'
import { IconButton } from '@mui/material'
import KeyboardArrowLeft from '@mui/icons-material/KeyboardArrowLeft'
import KeyboardArrowRight from '@mui/icons-material/KeyboardArrowRight'
import { styled, useTheme } from '@mui/system'
const Root = styled('div')(({ theme }) => ({
flexShrink: 0,
marginLeft: theme.spacing(2.5),
}))
const TablePaginationActions = (props) => {
const theme = useTheme()
const { count, page, rowsPerPage, onPageChange } = props
const handleFirstPageButtonClick = (event) => {
onPageChange(event, 0)
}
const handleBackButtonClick = (event) => {
onPageChange(event, page - 1)
}
const handleNextButtonClick = (event) => {
onPageChange(event, page + 1)
}
const handleLastPageButtonClick = (event) => {
onPageChange(event, Math.max(0, Math.ceil(count / rowsPerPage) - 1))
}
return (
<Root>
<IconButton
onClick={handleBackButtonClick}
disabled={page === 0}
aria-label="previous page"
data-cy={'table-pagination-actions-icon-button-prev'}
>
{theme.direction === 'rtl' ? (
<KeyboardArrowRight />
) : (
<KeyboardArrowLeft />
)}
</IconButton>
<IconButton
onClick={handleNextButtonClick}
disabled={page >= Math.ceil(count / rowsPerPage) - 1}
aria-label="next page"
data-cy={'table-pagination-actions-icon-button-next'}
>
{theme.direction === 'rtl' ? (
<KeyboardArrowLeft />
) : (
<KeyboardArrowRight />
)}
</IconButton>
</Root>
)
}
export default TablePaginationActions
我想说这是因为我的 useEffect
钩子被 useSWR
钩子以某种方式破坏了,但我无法证明这一点。
我正在尝试执行以下操作:
https://swr.vercel.app/docs/pagination
这是useSWRWithToken:
import useSWR from 'swr'
import fetchWithToken from 'libs/fetchWithToken'
import { useAppContext } from 'context'
function useSWRWithToken(endpoint: any, dependency: Boolean = true) {
const { state } = useAppContext()
const {
token: { accessToken },
} = state
const { data, error, mutate } = useSWR<any>(
// Make sure there is an accessToken and null does not appear in the uri
accessToken && endpoint.indexOf(null) === -1 && dependency
? endpoint
: null,
(url: string, params: object = null) =>
fetchWithToken(url, accessToken, params)
)
return {
data,
isLoading: !error && !data,
error,
mutate,
}
}
export default useSWRWithToken
这是患者 Table 组件:
import React, {
useEffect,
useState,
useMemo,
useCallback,
ReactChild,
} from 'react'
import { Paper } from '@mui/material'
import { LinearLoader } from 'components/loaders'
import { useRouter } from 'next/router'
import GeneralTable from 'components/table/GeneralTable'
import { parseInt, isNil } from 'lodash'
import MultiSelectChip, {
ChipCommonFilter,
} from 'components/forms/MultiSelectChip'
import usePermissions from 'hooks/usePermissions'
import { differenceInDays } from 'date-fns'
import { useAppContext } from 'context'
import { PatientsTableColumns } from 'components/table/patients/PatientsTableColumns'
import ProviderSelect from 'components/patients/ProviderSelect'
// TODO: declare interface types
interface IPatientList {
patientsList: any
patientsListError?: any
}
function PatientsTable({
patientsList = null,
patientsListError,
}: IPatientList) {
const router = useRouter()
const { state, dispatch } = useAppContext()
const { controlLevelFilters, selectedProviderFilter } = state.patientSearch
const [providerList, setProviderList] = useState<Array<string>>([])
const [dataParsed, setDataParsed] = useState([])
const [controlFilterOptions, setControlFilterOptions] = useState(['all'])
const [scroll, setScroll] = useState(false)
const { permissionPublicUserId }: { permissionPublicUserId: boolean } =
usePermissions()
const setControlLevelFilters = (value: string[]) => {
dispatch({
type: 'SET_CONTROL_LEVEL_FILTERS',
payload: {
controlLevelFilters: value,
},
})
}
const setSelectedProviderFilter = (provider: string) => {
console.log('provider: ', provider)
dispatch({
type: 'SET_SELECTED_PROVIDER_FILTER',
payload: {
selectedProviderFilter: provider,
},
})
}
const setSortState = (memoColumn: string, memoDirection: string) => {
dispatch({
type: 'SET_PATIENT_TABLE_SORT',
payload: {
columnName: memoColumn,
direction: !memoDirection ? 'asc' : 'desc',
},
})
}
const handleChangeControlLevelFilter = (
event: any,
child: ReactChild,
deleteValue: string
) => {
ChipCommonFilter(
event,
child,
deleteValue,
setControlLevelFilters,
controlLevelFilters
)
}
useEffect(() => {
dispatch({
type: 'SET_PAGE_HEADING',
payload: {
pageHeading1: 'Patients',
pageHeading2: `${
patientsList?.length ? `(${patientsList.length})` : ''
}`,
},
})
}, [patientsList])
useEffect(() => {
// Build up a list of patient objects which our table can traverse
const dataParsed = patientsList?.map((row) => {
!isNil(row.doctorDto) &&
setProviderList((previousProviderList) => [
...previousProviderList,
`${row.doctorDto.firstName} ${row.doctorDto.lastName}`,
])
const reportedDate = row.scalarReports.filter(
(obj) => obj.name === 'lastUseInDays'
)[0]?.reportedOn
const diffDate: number = Math.abs(
differenceInDays(new Date(reportedDate), new Date())
)
const lastUsed: string | number =
diffDate > 7
? diffDate
: diffDate === 0 && !isNil(reportedDate)
? 'Today'
: isNil(reportedDate)
? '--'
: diffDate
return {
pui: row.pui,
provider: !isNil(row.doctorDto)
? `${row.doctorDto.firstName} ${row.doctorDto.lastName}`
: '',
name: `${row.firstName} ${row.lastName}`,
dob: row.dob,
asthmaControl: row.scalarReports.filter(
(obj) => obj.name === 'asthmaControl'
)[0]?.value,
lastUsed,
fev1Baseline: row.scalarReports.filter(
(obj) => obj.name === 'fevBaseLine'
)[0]?.value,
}
})
setDataParsed(dataParsed)
}, [patientsList])
useEffect(() => {
window.addEventListener('scroll', () => {
setScroll(window.scrollY > 50)
})
}, [])
useEffect(() => {
if (dataParsed) {
setControlFilterOptions([
'all',
...(dataParsed.find((patient) => patient.asthmaControl === 'good')
? ['good']
: []),
...(dataParsed.find((patient) => patient.asthmaControl === 'poor')
? ['poor']
: []),
...(dataParsed.find((patient) => patient.asthmaControl === 'veryPoor')
? ['veryPoor']
: []),
])
}
}, [dataParsed])
const handleSelectProvider = (provider) => setSelectedProviderFilter(provider)
const isToday = (val) => val === 'Today' || val === 'today'
const isInactive = (val) => val === 'Inactive' || val === 'inactive'
const isDash = (val) => isNil(val) || val === '--'
const isAsthmaControl = (val) => {
const _val = val?.toString().toLowerCase()
let result: Number | boolean = false
switch (_val) {
case 'verypoor':
result = 1
break
case 'poor':
result = 2
break
case 'good':
result = 3
break
default:
result = false
}
return result
}
const CustomSortBy = useCallback(
(rowA, rowB, colId, direction) => {
const convertValue = (val) => {
if (isToday(val)) return 0 //Today != 1
if (isInactive(val)) return direction === false ? -2 : 29999
if (isDash(val)) return direction === false ? -3 : 30000
const acResult = isAsthmaControl(val) //so we don't call it twice
if (acResult) return acResult
return parseInt(val)
}
const v1 = convertValue(rowA.values[colId])
const v2 = convertValue(rowB.values[colId])
return v1 >= v2 ? 1 : -1 // Direction var doesn't matter.
},
[patientsList, isToday, isInactive, isDash]
)
const columns = useMemo(() => PatientsTableColumns(CustomSortBy), [])
const rowLinkOnClick = (patient) => {
router.push(`/patients/${patient.pui}`)
}
// TODO put better error here
if (patientsListError) return <div>Failed to load patients list</div>
if (!patientsList) return <LinearLoader />
return (
<Paper elevation={0} square>
{patientsList && dataParsed && (
<GeneralTable
retainSortState={true}
sortStateRecorder={setSortState}
columns={columns}
data={dataParsed}
checkRows={false}
rowLinkOnClick={rowLinkOnClick}
filters={[
{
column: 'asthmaControl',
options: controlLevelFilters,
},
{
column: 'provider',
options: selectedProviderFilter,
},
]}
initialState={{
sortBy: [
{
id: state.patientTableSort.columnName,
desc: state.patientTableSort.direction === 'desc',
},
],
hiddenColumns: false && !permissionPublicUserId ? [] : ['pui'], //For now, intentionally always hide
}}
leftContent={
<div style={{ display: 'flex' }}>
<MultiSelectChip
labelText="Asthma Control"
options={controlFilterOptions}
selectedValues={controlLevelFilters}
handleChange={handleChangeControlLevelFilter}
/>
<ProviderSelect
providers={Array.from(new Set(providerList))}
providerFilter={selectedProviderFilter}
handleChange={handleSelectProvider}
/>
</div>
}
/>
)}
</Paper>
)
}
export default PatientsTable
我还应该提到全局状态进入 appReducer
像这样:
import combineReducers from 'react-combine-reducers'
import { ReactElement } from 'react'
enum ActionName {
SET_PAGE_HEADING = 'SET_PAGE_HEADING',
SET_TIMER_RUNNING = 'SET_TIMER_RUNNING',
SET_PATIENT_DATA = 'SET_PATIENT_DATA',
SET_CHART_PERIOD = 'SET_CHART_PERIOD',
SET_TOKEN = 'SET_TOKEN',
}
enum ChartPeriod {
WEEK = 'week',
THREE_MONTH = '1m',
ONE_MONTH = 'week',
}
type Action = {
type: string
payload: any
}
// Types for global App State
type PageHeadingState = {
pageHeading1: string
pageHeading2?: string
component?: ReactElement
}
// TODO: fill this in or reference existing type
// types like this need to be centralized
type Patient = object
type PatientDataState = [
{
[key: string]: Patient
}
]
type PatientSearch = {
controlLevelFilters: string[]
selectedProviderFilter: string[]
}
type TimerState = {
running: boolean
visitId?: string | null
stopTimer?: () => void
}
type AppState = {
pageHeading: PageHeadingState
timer: TimerState
// TODO: flesh out what the shape of this data is and type it
// once the swagger definition is complete (state: PatientDataState)
patientData: PatientDataState
chartPeriod: ChartPeriod
patientSearch: PatientSearch
patientTableSort: PatientTableSort
token: object
}
// A reducer type to aggregate (n) reducers
type AppStateReducer = (state: AppState, action: Action) => AppState
// Initial State for the app
const initialPageHeading = {
pageHeading1: '',
pageHeading2: '',
}
const initialTimer = {
running: false,
}
const initialChartPeriod = ChartPeriod.WEEK
const initialPatientData: PatientDataState = [{}]
const initialPatientSearch: PatientSearch = {
controlLevelFilters: ['all'],
selectedProviderFilter: ['All Providers'],
}
type PatientTableSort = { columnName: string; direction: 'asc' | 'desc' }
const initialPatientTableSort: PatientTableSort = {
columnName: 'asthmaControl',
direction: 'desc',
}
// Perhaps can make this more explicit with STOP_PATIENT_CARE_TIMER
// and STOP_PATIENT_CARE_TIMER action cases
// I have kept the CRUD to a minimum for this first POC
const timerReducer = (state: TimerState, action: Action) => {
switch (action.type) {
case ActionName.SET_TIMER_RUNNING:
return { ...state, ...action.payload }
// case ActionName.STOP_PATIENT_CARE_TIMER:
// return { running: false }
default:
return state
}
}
const pageHeadingReducer = (state: PageHeadingState, action: Action) => {
switch (action.type) {
case ActionName.SET_PAGE_HEADING: {
return {
...state,
...action.payload,
}
}
default:
return state
}
}
// TODO: flesh out what the shape of this data is and type it
// once the swagger definition is complete (state: PatientDataState)
const patientDataReducer = (state: PatientDataState, action) => {
switch (action.type) {
case ActionName.SET_PATIENT_DATA: {
return action.payload
}
default:
return state
}
}
const chartPeriodReducer = (state: ChartPeriod, action: Action) => {
switch (action.type) {
case ActionName.SET_CHART_PERIOD: {
return action.payload
}
default:
return state
}
}
const patientSearchReducer = (state: PatientSearch, action: Action) => {
switch (action.type) {
case 'SET_SELECTED_PROVIDER_FILTER': {
return { ...state, ...action.payload }
}
case 'SET_CONTROL_LEVEL_FILTERS': {
return { ...state, ...action.payload }
}
default:
return state
}
}
const patientTableSortReducer = (state: PatientTableSort, action: Action) => {
switch (action.type) {
case 'SET_PATIENT_TABLE_SORT': {
return { ...state, ...action.payload }
}
case 'CLEAR_PATIENT_TABLE_SORT': {
const update = { columnName: '', direction: 'asc' }
return { ...state, ...update }
}
default:
return state
}
}
const tokenReducer = (state: object, action: Action) => {
switch (action.type) {
case 'SET_TOKEN': {
return action.payload
}
default:
return state
}
}
// This is exposed for use in AppContext.tsx so bootstrap our store/state
export const [AppReducer, initialState] = combineReducers<AppStateReducer>({
pageHeading: [pageHeadingReducer, initialPageHeading],
timer: [timerReducer, initialTimer],
patientData: [patientDataReducer, initialPatientData],
chartPeriod: [chartPeriodReducer, initialChartPeriod],
patientSearch: [patientSearchReducer, initialPatientSearch],
patientTableSort: [patientTableSortReducer, initialPatientTableSort],
token: [tokenReducer, {}],
})
由于你的全局缓存应该考虑当前页面来管理你的数据,这个数据应该出现在键中,然后你必须像useSWRWithToken(`/patients/?page${currentPage}`)
一样通过你的钩子发送它然后每次当前页面更改 SWR 将触发新的提取。
我建议不要使用字符串作为键,而是使用包含所有数据的数组,例如
const fetcher = async (key: string, id: string, pagination: number) => {
//He in your fetch you'll receive the key ordered by the array
fetchWithToken(key, accessToken, [id, page: pagination])
}
const useSWRWithToken = (key, id, pagination) = {
const { data, error, isValidating } = useSWR(
[`patients/${id}`, id, pagination], fetcher, options
)
}