Skip to content

Commit

Permalink
Merge pull request #7 from sheinsight/feat/use-infinite-list
Browse files Browse the repository at this point in the history
feat: useWebSocket & useForm & usePagingList & useInfiniteList
  • Loading branch information
vikiboss authored Sep 11, 2024
2 parents dbd46ac + 20d4fd8 commit adc459c
Show file tree
Hide file tree
Showing 36 changed files with 1,283 additions and 294 deletions.
4 changes: 4 additions & 0 deletions biome.json
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,10 @@
},
"suspicious": {
"noExplicitAny": "off"
},
"a11y": {
"useKeyWithClickEvents": "off",
"useKeyWithMouseEvents": "off"
}
}
},
Expand Down
2 changes: 1 addition & 1 deletion docs/scripts/generate-hooks-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const __filename = fileURLToPath(import.meta.url)
const __dirname = dirname(__filename)

const hooksSrc = resolve(__dirname, '../../packages/react-use/src')
const ignoredDirs = ['utils', 'use-track-ref-state', 'use-versioned-action', 'use-web-observer']
const ignoredDirs = ['utils', 'use-track-ref-state', 'use-web-observer']

const dirents = await fs.readdir(hooksSrc, { withFileTypes: true })
const hooksDirents = dirents.filter((d) => d.isDirectory() && ignoredDirs.every((e) => e !== d.name))
Expand Down
1 change: 1 addition & 0 deletions packages/react-use/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ export * from './use-fullscreen'
export * from './use-geolocation'
export * from './use-getter-ref'
export * from './use-hover'
export * from './use-infinite-list'
export * from './use-infinite-scroll'
export * from './use-input-composition'
export * from './use-intersection-observer'
Expand Down
2 changes: 1 addition & 1 deletion packages/react-use/src/use-async-effect/index.zh-cn.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { HooksType } from '@/components'

:::

## 场景 Scenes \{#scenes}
## 场景 \{#scenes}

- **异步数据请求场景:** 实现页面加载时或依赖项更改时的异步数据请求
- **状态更新监控场景:** 在依赖项状态更新后执行异步状态同步更新操作
Expand Down
2 changes: 1 addition & 1 deletion packages/react-use/src/use-boolean/index.zh-cn.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { HooksType, Since } from '@/components'

:::

## 场景 Scenes \{#scenes}
## 场景 \{#scenes}

- **控制元素显隐场景:** 用于控制页面元素如对话框、下拉菜单的显示和隐藏
- **表单开关输入场景:** 管理表单中开关类型输入(如复选框)的状态
Expand Down
2 changes: 1 addition & 1 deletion packages/react-use/src/use-clamp/index.zh-cn.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { HooksType } from '@/components'

本质上,它只是设置了 `min``max` 选项的 [useCounter](/reference/use-counter) 的更加语意化的版本。

## 场景 Scenes \{#scenes}
## 场景 \{#scenes}

- **处理数量计算场景:** 提供增加、减少、设置、获取、重置计数器的功能
- **界面交互场景:** 实现用户界面上数量的动态更新和显示,如购物车商品数量、轮播图切换
Expand Down
2 changes: 1 addition & 1 deletion packages/react-use/src/use-cloned-state/index.zh-cn.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { HooksType } from '@/components'

一个用来创建支持修改、同步操作、相互隔离的克隆状态的 React Hook,支持自定义 `clone` 函数,默认使用 `JSON.parse(JSON.stringify(source))`

## 场景 Scenes \{#scenes}
## 场景 \{#scenes}

- **数据状态克隆与隔离场景:** 实现数据的深拷贝,创建独立状态,用于编辑不影响原始数据
- **编辑历史记录场景:** 维护数据的编辑历史,支持撤销、重做功能
Expand Down
2 changes: 1 addition & 1 deletion packages/react-use/src/use-counter/index.zh-cn.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { HooksType } from '@/components'

一个提供包含增加、减少和重置功能的计数器的 React Hook。

## 场景 Scenes \{#scenes}
## 场景 \{#scenes}

- **处理数量计算场景:** 提供增加、减少、设置、获取、重置计数器的功能
- **界面交互场景:** 实现用户界面上数量的动态更新和显示,如购物车商品数量、轮播图切换
Expand Down
221 changes: 221 additions & 0 deletions packages/react-use/src/use-infinite-list/demo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
import { Button, Card, Input, KeyValue, Zone, cn, wait } from '@/components'
import { generateLoremIpsum, useInfiniteList, useUpdateEffect } from '@shined/react-use'
import { useRef } from 'react'

interface Data {
data: { id: number; name: string }[]
total: number
}

const genders = ['Boy', 'Girl'] as const
const colors = ['Red', 'Orange', 'Yellow', 'Green', 'Cyan', 'Blue', 'Violet'] as const

export function App() {
const ref = useRef<HTMLDivElement>(null)

const { form, list, fullList, paginationState, selection, loading, isLoadDone } = useInfiniteList<
Data,
Data['data'][number],
{ name: string; gender: string; color: string[] }
>({
target: ref,
fetcher: fetchPagination,
mapFullList: (d) => d.data,
canLoadMore: (previousData, dataList, fullList) => {
if (!previousData) return true // initial load
return fullList.length < previousData.total
},
form: {
initialValue: {
name: '',
gender: 'Boy',
color: ['Red'],
},
},
pagination: { pageSize: 10 },
immediateQueryKeys: ['color', 'gender'],
})

// when you use third-party components, you can use `selection.isPartiallySelected` directly
useUpdateEffect(() => {
const selectAllInput = document.querySelector('input[name="select-all"]') as HTMLInputElement
selectAllInput.indeterminate = selection.isPartiallySelected
}, [selection.isPartiallySelected])

return (
<Card>
<h1 className="text-xl font-medium mt-4">1. Scroll to Load More</h1>
<form {...form.nativeProps}>
<Zone border="amber">
<Zone>
<span className="text-right inline-block w-80px">Name:</span>
<Input name="name" placeholder="Name" />
<Button disabled={loading} type="submit">
Search
</Button>
<Button disabled={loading} type="reset">
Reset
</Button>
</Zone>
<Zone>
<span className="text-right inline-block w-80px">Gender:</span>
{genders.map((gender) => (
<label key={gender}>
<input name="gender" type="radio" required value={gender} />
<span className="ml-1">{gender}</span>
</label>
))}
</Zone>
<Zone>
<span className="text-right inline-block w-80px">Color:</span>
{colors.map((color) => (
<label key={color}>
<input name="color" type="checkbox" value={color} />
<span className="ml-1">{color}</span>
</label>
))}
</Zone>
</Zone>
</form>
<Zone border="primary">
<KeyValue label="Current Page" value={paginationState.page - 1} />
<KeyValue label="Next Request Page" value={paginationState.page} />
<KeyValue label="PageSize" value={paginationState.pageSize} />
<KeyValue label="Data Count" value={list.flatMap((e) => e.data).length} />
</Zone>
<Zone border="blue">
<label>
<input
name="select-all"
type="checkbox"
checked={selection.isAllSelected}
onChange={(e) => {
selection[e.target.checked ? 'selectAll' : 'unselectAll']()
}}
/>
<span className="ml-1">Select All</span>
</label>
<Button
disabled={loading}
onClick={() => {
selection.unselectAll()
const [item1, _, item3] = fullList
item1 && selection.select(item1)
item3 && selection.select(item3)
}}
>
Select 1, 3
</Button>
<Button disabled={loading} onClick={() => selection.selectAll()}>
Select All
</Button>
<Button disabled={loading} onClick={() => selection.unselectAll()}>
Unselect All
</Button>
<KeyValue label="Loading" value={loading} />
<KeyValue label="Load Done" value={isLoadDone} />
</Zone>
<div
ref={ref}
className={cn(
'flex flex-col gap-2 h-300px p-2 w-full rounded bg-white/12 flex-nowrap overflow-scroll transition-all',
loading ? 'opacity-60' : '',
)}
>
{fullList.map((item) => {
return (
<div
key={item.id}
className={cn(
'px-2 py-1 rounded cursor-pointer',
selection.isItemSelected(item) ? 'bg-primary/60' : 'hover:bg-primary/20',
)}
onClick={() => selection.toggle(item)}
>
<input
className="mr-2"
type="checkbox"
onChange={() => {
selection.toggle(item)
}}
checked={selection.isItemSelected(item)}
/>
{item.id} - {item.name}
</div>
)
})}

{loading && <div className="text-center my-1 py-1 dark:text-white animate-pulse">Loading...</div>}
{isLoadDone && <div className="text-center my-1 py-1 dark:text-white/60">No more data</div>}
</div>

<Zone border="red">
{selection.selected.map((item) => (
<div key={item.id}>{item.name}</div>
))}
{selection.selected.length === 0 && <div className="text-center">No selected</div>}
</Zone>

<hr />

<h1 className="text-xl font-medium mt-4">2. Click to Load More</h1>
<LoadMoreList />
</Card>
)
}

function LoadMoreList() {
const { loadMore, fullList, loading, isLoadDone } = useInfiniteList<Data, Data['data'][number]>({
fetcher: fetchPagination,
mapFullList: (d) => d.data,
canLoadMore: (previousData, dataList, fullList) => {
if (!previousData) return true // initial load
return fullList.length < previousData.total
},
})

return (
<div
className={cn(
'flex flex-col gap-2 h-300px p-2 w-full rounded bg-white/12 flex-nowrap overflow-scroll transition-all',
)}
>
{fullList.map((item) => {
return (
<div key={item.id} className={cn('px-2 py-1 rounded hover:bg-primary/20')}>
{item.id} - {item.name}
</div>
)
})}

{isLoadDone ? (
<div className="text-center my-1 py-1 dark:text-white/60">No more data</div>
) : (
<Button
className={cn('text-center my-1 py-1 dark:text-white', loading ? 'animate-pulse' : '')}
onClick={() => loadMore()}
>
{loading ? 'Loading...' : 'Load More'}
</Button>
)}
</div>
)
}

async function fetchPagination(params: { page: number; pageSize: number }): Promise<Data> {
await wait(600)

const total = 57
const isLastPage = params.page * params.pageSize >= total && (params.page - 1) * params.pageSize < total

const startIdx = (params.page - 1) * params.pageSize
const returnLength = isLastPage ? total - startIdx : params.pageSize

return {
data: Array.from({ length: returnLength }).map((_, i) => ({
id: startIdx + i + 1,
name: generateLoremIpsum(),
})),
total,
}
}
Loading

0 comments on commit adc459c

Please sign in to comment.