Skip to content

Data Fetching Patterns

Problem

You need to fetch, cache, mutate, and display server data with proper loading states, error handling, retry logic, and optimistic updates.

Solution

Use createResource for reads and createAction for writes. Both integrate with AkashJS signals for reactive UI updates.

1. Basic Resource

ts
import { createResource } from '@akashjs/http';
import { createHttpClient } from '@akashjs/http';

const http = createHttpClient({ baseUrl: '/api' });

// Fetches once, re-fetches when userId changes
const userId = signal(1);
const user = createResource(
  () => http.get(`/users/${userId()}`),
  { key: () => userId() }
);
html
<div :if={user.loading()} class="skeleton">Loading...</div>
<div :if={user.error()} class="error">
  {user.error().message}
  <button @click={user.refetch}>Retry</button>
</div>
<div :if={user()}>
  <h1>{user().name}</h1>
  <p>{user().email}</p>
</div>

2. Caching with staleTime

ts
// Data stays fresh for 30 seconds — navigating back skips refetch
const posts = createResource(
  () => http.get('/posts'),
  { staleTime: 30_000 }
);

// Refetch when the browser tab regains focus
const notifications = createResource(
  () => http.get('/notifications'),
  { staleTime: 10_000, refetchOnFocus: true }
);

TIP

Set staleTime to avoid redundant fetches. A good default is 30 seconds for list views and 60 seconds for detail views.

3. Mutations with createAction

ts
import { createAction } from '@akashjs/http';

const createPost = createAction(
  (data: { title: string; body: string }) =>
    http.post('/posts', data),
  {
    onSuccess: (result) => {
      posts.refetch(); // Refresh the list
    },
    onError: (err) => {
      console.error('Failed to create post:', err.message);
    },
  }
);
html
<form @submit|preventDefault={handleSubmit}>
  <!-- form fields... -->
  <button type="submit" :disabled={createPost.loading()}>
    {createPost.loading() ? 'Saving...' : 'Create Post'}
  </button>
  <p :if={createPost.error()} class="error">{createPost.error().message}</p>
</form>

4. Optimistic Updates

ts
const todos = createResource(() => http.get('/todos'));

const toggleTodo = createAction(
  (todo: Todo) => http.patch(`/todos/${todo.id}`, { done: !todo.done }),
  {
    optimistic: (todo) => {
      // Immediately update the UI
      todos.mutate(
        todos().map((t) =>
          t.id === todo.id ? { ...t, done: !t.done } : t
        )
      );
    },
    revertOptimistic: () => {
      // On failure, refetch the real data
      todos.refetch();
    },
  }
);

WARNING

Always provide revertOptimistic when using optimistic. Without it, a failed mutation leaves the UI in an incorrect state.

5. Retry with Exponential Backoff

ts
import { retry } from '@akashjs/http';

const data = await retry(
  () => http.get('/flaky-endpoint'),
  {
    maxRetries: 3,
    backoff: 'exponential',
    delay: 1000,
    maxDelay: 10000,
    jitter: true,
    retryOn: [500, 502, 503, 429],
    onRetry: (err, attempt) => {
      console.log(`Retry ${attempt}: ${err.message}`);
    },
  }
);

6. Loading Skeletons

html
<div :if={user.loading()} class="skeleton-card">
  <div class="skeleton-line skeleton-title"></div>
  <div class="skeleton-line"></div>
  <div class="skeleton-line skeleton-short"></div>
</div>
css
.skeleton-line {
  height: 1rem;
  background: linear-gradient(90deg, #eee 25%, #ddd 50%, #eee 75%);
  background-size: 200% 100%;
  animation: shimmer 1.5s infinite;
  border-radius: 4px;
  margin-bottom: 0.75rem;
}
.skeleton-title { width: 60%; height: 1.5rem; }
.skeleton-short { width: 40%; }
@keyframes shimmer { to { background-position: -200% 0; } }

7. Request Deduplication

ts
// createResource automatically deduplicates: if the same key triggers
// two fetches, the first in-flight request is cancelled via AbortController
const userId = signal(1);

const user = createResource(
  () => http.get(`/users/${userId()}`),
  { key: () => userId() }
);

// Rapidly changing userId only fires one request (the latest)
userId.set(2);
userId.set(3); // Only this request completes

When to Use What

PatternUse CaseAPI
Read onceStatic data on page loadcreateResource(fetcher)
Read + rekeyData dependent on paramscreateResource(fetcher, { key })
Cached readAvoid redundant fetchescreateResource(fetcher, { staleTime })
Manual refetchRefresh after mutationresource.refetch()
WriteCreate, update, deletecreateAction(mutationFn)
Optimistic writeInstant UI feedbackcreateAction(fn, { optimistic })
Resilient fetchFlaky APIsretry(fn, { maxRetries })

Result

A complete data layer with reactive reads, write mutations, caching, optimistic updates, exponential retry, and automatic request deduplication. Loading and error states are signals, so your UI reacts instantly.

Released under the MIT License.