How to avoid rerenders when using refetch ?

Hi, I'm trying to build a simple todo list app using Solid. Everything has been fine so far, I really enjoy using Solid! When I decided to add CSS transitions when adding an item to the todo list, I noticed that all items in the list were re-rendered each time they were added. After some investigation, I found that this re-rendering was caused by refetch, which when called causes a re-rendering of the entire list. Using mutate works as expected, so I suspect that the reactivity is lost because the list is replaced with a whole new object. What's the best way to avoid this kind of re-renders?
// api.ts

const [todos, { mutate, refetch }] = createResource(fetchTodos)

async function fetchTodos(): Promise<Todo[]> {
const api = useApi()
return await api<Todo[]>('/user/todos')
}

export async function createTodo(
todo: { title: string; description: string; dueTime: Date },
): Promise<Todo> {
// ... (constructing request)

const createdTodo = await api<Todo>(
'/todos',
{ method: 'POST', body: requestBody },
)

mutate(todos => [...todos ?? [], createdTodo])
refetch() // <- this call causes the re-render

return createdTodo
}

export { todos as userTodos }
// api.ts

const [todos, { mutate, refetch }] = createResource(fetchTodos)

async function fetchTodos(): Promise<Todo[]> {
const api = useApi()
return await api<Todo[]>('/user/todos')
}

export async function createTodo(
todo: { title: string; description: string; dueTime: Date },
): Promise<Todo> {
// ... (constructing request)

const createdTodo = await api<Todo>(
'/todos',
{ method: 'POST', body: requestBody },
)

mutate(todos => [...todos ?? [], createdTodo])
refetch() // <- this call causes the re-render

return createdTodo
}

export { todos as userTodos }
// TodoList.tsx

// ...

const TodoListPart: Component<{ name: string; status: TodoStatus }> = (props) => {
const filteredTodos = () => userTodos()?.filter(todo => todo.status === props.status) ?? []

return (
<div class="mt-8">
<h2 class="text-lg font-bold">{props.name}</h2>
<For each={filteredTodos()} fallback={<EmptyPart />}>
{todo => <TodoItem title={todo.title} status={todo.status} />}
</For>
</div>
)
}

const TodosList: Component = () => {
return (
<Suspense fallback={<Loading />}>
<TodoListPart name="Not started" status={TodoStatus.NOT_STARTED} />
<TodoListPart name="Todo" status={TodoStatus.TODO} />
<TodoListPart name="In progress" status={TodoStatus.IN_PROGRESS} />
<TodoListPart name="Done" status={TodoStatus.DONE} />
</Suspense>
)
}

export default TodosList
// TodoList.tsx

// ...

const TodoListPart: Component<{ name: string; status: TodoStatus }> = (props) => {
const filteredTodos = () => userTodos()?.filter(todo => todo.status === props.status) ?? []

return (
<div class="mt-8">
<h2 class="text-lg font-bold">{props.name}</h2>
<For each={filteredTodos()} fallback={<EmptyPart />}>
{todo => <TodoItem title={todo.title} status={todo.status} />}
</For>
</div>
)
}

const TodosList: Component = () => {
return (
<Suspense fallback={<Loading />}>
<TodoListPart name="Not started" status={TodoStatus.NOT_STARTED} />
<TodoListPart name="Todo" status={TodoStatus.TODO} />
<TodoListPart name="In progress" status={TodoStatus.IN_PROGRESS} />
<TodoListPart name="Done" status={TodoStatus.DONE} />
</Suspense>
)
}

export default TodosList
4 Replies
Tommypop
Tommypop2y ago
The rerenders are likely due to the resource retriggering the nearest suspense boundary on refetch (which is, by default, at the root of your app). If you wrap where the resource is accessed in a <Suspense> then only that boundary will be triggered and the rest of your app won't be rerendered I believe you can also use data.latest instead of data() to access the resource, then the latest value will always be displayed so the suspense boundary won't retrigger
baptiste0928
baptiste0928OP2y ago
I already used a Suspense in my TodoList component, but since the resource contains all the todos, the whole list is re-rendered on refetch. I found reconcile in the documentation which seems to be able to diff data changes and trigger renders only on what changed, can this be used with resources ?
Tommypop
Tommypop2y ago
It might be easiest to use .latest then, as that means the latest data is always shown, even when you're refetching .latest doesn't trigger suspenses and only updates when the data changes (after a refetch for example)
foolswisdom
foolswisdom2y ago
BTW to be clear, this is not technically a rerender of the component, but rather that the suspense boundary temporarily removes it from the DOM till the resource loads (but doesn't gets deleted and recreated)
Want results from more Discord servers?
Add your server