Skip to content

Instantly share code, notes, and snippets.

@sibbng
Last active May 7, 2025 17:38
Show Gist options
  • Select an option

  • Save sibbng/da7b99355f768d5dc33ccbc1d9aeb2b6 to your computer and use it in GitHub Desktop.

Select an option

Save sibbng/da7b99355f768d5dc33ccbc1d9aeb2b6 to your computer and use it in GitHub Desktop.
{
"imports": {
"voby": "https://esm.sh/voby",
"voby-query": "https://esm.sh/voby-query"
}
}
@theme {
--font-sans: 'Inter', sans-serif;
}
@custom-variant dark (&:where(.dark, .dark *));
@layer {
body {
@apply bg-neutral-400;
}
}
import { render, If, $, Observable, useEffect } from 'voby';
import { createQueryClient, QueryClientProvider, useQuery } from 'voby-query';
interface Todo {
userId: number;
id: number;
title: string;
completed: boolean;
}
const fetchTodo = async (id: number): Promise<Todo> => {
const url = `https://jsonplaceholder.typicode.com/todos/${id}`;
console.log(`Fetching todo ${id}...`);
const response = await fetch(url);
if (!response.ok) {
if (response.status === 404) {
throw new Error(`Todo with ID ${id} not found.`);
}
throw new Error(`HTTP error! Status: ${response.status}`);
}
const data: Todo = await response.json();
return data;
};
const SimpleTodo = (): JSX.Element => {
const todoId: Observable<number> = $(1);
const query = useQuery({
queryKey: ['todo', todoId],
queryFn: () => fetchTodo(todoId()),
});
useEffect(() => {
console.log("Query data:", query().data())
})
const handleRefetch = () => {
query().refetch();
};
const handleNextTodo = () => {
todoId(id => id + 1);
};
return (
<div class="bg-white max-w-lg m-auto mt-20 p-4 rounded-xl">
<h1>Simple Todo Fetch</h1>
<p>Current Todo ID: {todoId}</p>
<div style={{ marginBottom: '1rem', display: 'flex', gap: '0.5rem' }}>
<button class="bg-blue-600 rounded px-3 text-white" onClick={handleRefetch} disabled={() => query().isPending()}>
{() => query().isPending() ? 'Refreshing...' : 'Refetch Current'}
</button>
<button class="bg-blue-600 rounded px-3 text-white" onClick={handleNextTodo} disabled={() => query().isPending()}>
{() => query().isPending() ? 'Loading Next...' : 'Next Todo'}
</button>
<button class="bg-blue-600 rounded px-3 text-white" onClick={() => todoId(id => id - 1)} disabled={() => query().isPending()}>
{() => query().isPending() ? 'Loading Prev...' : 'Previous Todo'}
</button>
</div>
<div style={{ minHeight: '150px', position: 'relative' }}>
<If when={() => query().isPending()}>
<p style={{ color: '#888' }}>Loading...</p>
</If>
<If when={() => query().isError()}>
<p style={{ color: 'red' }}>Error fetching todo: {() => query().error()?.message ?? 'Unknown error'}</p>
</If>
<If when={() => query().isSuccess() && query().data()}>
{(data) => (
<div style={{ opacity: query().isFetching() ? 0.6 : 1, transition: 'opacity 0.2s ease-in-out' }}>
<h2>Todo Item #{() => data().id}</h2>
<p><strong>Todo title:</strong> {() => data().title}</p>
<p><strong>Todo status:</strong> {() => data().completed ? 'Completed' : 'Pending'}</p>
</div>
)}
</If>
</div>
</div>
);
};
const queryClient = createQueryClient();
const App = () => (
<QueryClientProvider value={queryClient}>
<SimpleTodo />
</QueryClientProvider>
);
render(<App />, document.getElementById('app'));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment