Created
October 12, 2025 04:40
-
-
Save sergiodxa/7d329856b5f7a426dcb3afd4e69bb1f9 to your computer and use it in GitHub Desktop.
An example on how to use Remix v3 along TanStack Query
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { hydrated, type Remix } from "@remix-run/dom"; | |
import { press } from "@remix-run/events/press"; | |
import { | |
QueryClient, | |
QueriesObserver, | |
type QueryObserverOptions, | |
} from "@tanstack/query-core"; | |
type Todo = { | |
userId: number; | |
id: number; | |
title: string; | |
completed: boolean; | |
}; | |
export const fetchTodosQuery = { | |
queryKey: ["todos"], | |
async queryFn({ signal }: { signal?: AbortSignal }) { | |
await new Promise((resolve) => setTimeout(resolve, 1000)); | |
let response = await fetch("https://jsonplaceholder.typicode.com/todos", { | |
signal, | |
}); | |
return (await response.json()) as Todo[]; | |
}, | |
}; | |
export const TodoList = hydrated<{ todos: Todo[] }>( | |
"/dist/todo-list.js#TodoList", | |
function TodoList({ todos }) { | |
let queryClient = new QueryClient(); | |
queryClient.setQueryData(["todos"], todos); // set initial data | |
// subscribe to changes in the query and re-render when it changes | |
observeQuery(this, queryClient, [fetchTodosQuery]); | |
return () => { | |
let state = queryClient.getQueryState<Todo[]>(["todos"]); | |
let isFetching = state?.fetchStatus === "fetching"; | |
return ( | |
<div> | |
<button | |
disabled={isFetching} | |
type="button" | |
on={[press(() => queryClient.refetchQueries(fetchTodosQuery))]} | |
> | |
{isFetching ? "Loading…" : "Fetch Todos"} | |
</button> | |
<ul> | |
{state?.data?.map((todo) => ( | |
<TodoItem key={todo.id} todo={todo} /> | |
))} | |
</ul> | |
</div> | |
); | |
}; | |
}, | |
); | |
function observeQuery( | |
handle: Remix.Handle, | |
queryClient: QueryClient, | |
queries: Array<QueryObserverOptions<any, any, any, any, any>>, | |
) { | |
let observer = new QueriesObserver(queryClient, queries); | |
let unsubscribe = observer.subscribe(() => { | |
// if for some reason this runs after the component is unmounted, | |
// unsubscribe and return | |
if (handle.signal.aborted) return unsubscribe(); | |
handle.update(); // trigger a re-render on every query change | |
}); | |
// unsubscribe when component is removed | |
handle.signal.addEventListener("abort", unsubscribe, { once: true }); | |
} | |
function TodoItem({ todo }: { todo: Todo }) { | |
return ( | |
<li> | |
<label> | |
<input type="checkbox" checked={todo.completed} /> | |
<span>{todo.title}</span> | |
</label> | |
</li> | |
); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment