145 lines
3.7 KiB
Markdown
145 lines
3.7 KiB
Markdown
# perf-select-transform: Use Select to Transform and Filter Data
|
|
|
|
## Priority: LOW
|
|
|
|
## Explanation
|
|
|
|
The `select` option transforms query data before it reaches your component. Use it for filtering, sorting, or deriving data. Benefits include memoization (re-runs only when data changes) and reduced component re-renders.
|
|
|
|
## Bad Example
|
|
|
|
```tsx
|
|
// Transforming in component - runs on every render
|
|
function CompletedTodos() {
|
|
const { data: todos } = useQuery({
|
|
queryKey: ['todos'],
|
|
queryFn: fetchTodos,
|
|
})
|
|
|
|
// This filtering runs on every render
|
|
const completedTodos = todos?.filter(todo => todo.completed) ?? []
|
|
const sortedTodos = [...completedTodos].sort((a, b) =>
|
|
new Date(b.completedAt).getTime() - new Date(a.completedAt).getTime()
|
|
)
|
|
|
|
return <TodoList todos={sortedTodos} />
|
|
}
|
|
```
|
|
|
|
## Good Example
|
|
|
|
```tsx
|
|
// Using select - runs only when data changes
|
|
function CompletedTodos() {
|
|
const { data: completedTodos } = useQuery({
|
|
queryKey: ['todos'],
|
|
queryFn: fetchTodos,
|
|
select: (todos) =>
|
|
todos
|
|
.filter(todo => todo.completed)
|
|
.sort((a, b) =>
|
|
new Date(b.completedAt).getTime() - new Date(a.completedAt).getTime()
|
|
),
|
|
})
|
|
|
|
return <TodoList todos={completedTodos ?? []} />
|
|
}
|
|
```
|
|
|
|
## Good Example: Selecting Specific Fields
|
|
|
|
```tsx
|
|
// Derive computed values
|
|
function TodoStats() {
|
|
const { data: stats } = useQuery({
|
|
queryKey: ['todos'],
|
|
queryFn: fetchTodos,
|
|
select: (todos) => ({
|
|
total: todos.length,
|
|
completed: todos.filter(t => t.completed).length,
|
|
pending: todos.filter(t => !t.completed).length,
|
|
completionRate: todos.length
|
|
? (todos.filter(t => t.completed).length / todos.length) * 100
|
|
: 0,
|
|
}),
|
|
})
|
|
|
|
return (
|
|
<div>
|
|
<span>{stats?.completed} / {stats?.total} completed</span>
|
|
<span>({stats?.completionRate.toFixed(1)}%)</span>
|
|
</div>
|
|
)
|
|
}
|
|
```
|
|
|
|
## Good Example: Stable Select with useCallback
|
|
|
|
```tsx
|
|
// When select depends on external values, stabilize with useCallback
|
|
function FilteredTodos({ status }: { status: 'all' | 'active' | 'completed' }) {
|
|
const selectTodos = useCallback(
|
|
(todos: Todo[]) => {
|
|
switch (status) {
|
|
case 'active':
|
|
return todos.filter(t => !t.completed)
|
|
case 'completed':
|
|
return todos.filter(t => t.completed)
|
|
default:
|
|
return todos
|
|
}
|
|
},
|
|
[status]
|
|
)
|
|
|
|
const { data: filteredTodos } = useQuery({
|
|
queryKey: ['todos'],
|
|
queryFn: fetchTodos,
|
|
select: selectTodos,
|
|
})
|
|
|
|
return <TodoList todos={filteredTodos ?? []} />
|
|
}
|
|
```
|
|
|
|
## Good Example: Picking Single Item from List
|
|
|
|
```tsx
|
|
// Select single item from cached list
|
|
function useTodoById(id: number) {
|
|
return useQuery({
|
|
queryKey: ['todos'],
|
|
queryFn: fetchTodos,
|
|
select: (todos) => todos.find(todo => todo.id === id),
|
|
})
|
|
}
|
|
|
|
// Usage - shares cache with list query
|
|
function TodoDetail({ id }: { id: number }) {
|
|
const { data: todo } = useTodoById(id)
|
|
|
|
if (!todo) return <div>Todo not found</div>
|
|
return <div>{todo.title}</div>
|
|
}
|
|
```
|
|
|
|
## When to Use Select
|
|
|
|
| Scenario | Use Select? |
|
|
|----------|-------------|
|
|
| Filtering list data | Yes |
|
|
| Sorting data | Yes |
|
|
| Computing derived values | Yes |
|
|
| Picking single item from list | Yes |
|
|
| Heavy transformations | Yes (memoized) |
|
|
| Simple data pass-through | No |
|
|
| Transformation needs external state | Yes, with useCallback |
|
|
|
|
## Context
|
|
|
|
- `select` leverages structural sharing - only re-runs when data actually changes
|
|
- Original query data stays cached; transformation applies to consumer
|
|
- Multiple components can use different `select` on the same query
|
|
- Avoid unstable function references - use `useCallback` when needed
|
|
- For complex transformations, consider useMemo in component instead if readability suffers
|