Fetch API
The fetch API provides reactive state management for HTTP requests. It wraps the standard fetch
API with Anchor's reactivity system, making it easy to handle loading states, errors, and data updates.
Optimistic Model
The fetch API uses an optimistic model where the initial state is used to display loading states. This means that the initial state is displayed while the request is pending. It's also useful to build a skeleton UI before the request completes.
fetchState
Create a reactive fetch state object that automatically updates as the HTTP request progresses.
Type Signature
type fetchState = <T, S extends LinkableSchema = LinkableSchema>(init: T, options: FetchOptions<S>) => FetchState<T>;
Parameters
init
- The initial data value for the stateoptions
- Configuration options including URL and standardRequestInit
parameters
Return Value
Returns a reactive FetchState
object with the following properties:
data
- The fetched data (initially the provided initial value)status
- Current request status (pending
,success
, orerror
)error
- Error object if the request failedresponse
- Raw Response object from the fetch API
Options
The options object extends the standard RequestInit
interface and includes:
url
- The URL to fetch (string or URL object)- All standard
RequestInit
properties like method, headers, body, etc.
Usage
import { fetchState, derive } from '@anchorlib/core';
// Create a reactive fetch state
const userState = fetchState(
null, // Initial data
{
url: 'https://api.example.com/users/1',
method: 'GET',
headers: {
Authorization: 'Bearer token',
},
}
);
// Subscribe to state changes
derive(userState, (state) => {
switch (state.status) {
case 'pending':
console.log('Loading...');
break;
case 'success':
console.log('User data:', state.data);
break;
case 'error':
console.error('Error:', state.error);
break;
}
});
fetchState.promise
Convert a fetch state object to a Promise for traditional async/await usage.
Type Signature
type promise = <T, S extends FetchState<T>>(state: S) => Promise<S>;
Parameters
state
- A fetch state object created by [fetchState]
Return Value
Returns a Promise that resolves with the final state when the request succeeds, or rejects with the error when the request fails.
Usage
import { fetchState } from '@anchorlib/core';
const userState = fetchState(null, { url: '/api/users/1' });
try {
const finalState = await fetchState.promise(userState);
console.log('User data:', finalState.data);
} catch (error) {
console.error('Request failed:', error);
}
FetchStatus Enum
The FetchStatus
enum defines the possible states of a fetch request:
Pending
- Request has been initiated but not yet completedSuccess
- Request completed successfullyError
- Request failed with an error
Examples
Fetching JSON Data
import { fetchState } from '@anchorlib/core';
interface User {
id: number;
name: string;
email: string;
}
const userState = fetchState<User | null>(null, {
url: '/api/users/1',
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});
// Using with derive to react to state changes
derive(userState, (state) => {
if (state.status === 'success' && state.data) {
document.getElementById('user-name')!.textContent = state.data.name;
}
});
Posting Data
import { fetchState } from '@anchorlib/core';
interface User {
id: number;
name: string;
email: string;
}
const createUserState = fetchState<User | null>(null, {
url: '/api/users',
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: 'John Doe',
email: 'john@example.com',
}),
});
Error Handling
import { fetchState, derive } from '@anchorlib/core';
const dataState = fetchState([], { url: '/api/data' });
derive(dataState, (state) => {
switch (state.status) {
case 'pending':
showLoadingIndicator();
break;
case 'success':
hideLoadingIndicator();
renderData(state.data);
break;
case 'error':
hideLoadingIndicator();
showError(state.error?.message || 'An error occurred');
break;
}
});
Best Practices
1. Always Handle All Status States
Make sure to handle all possible status states in your UI to provide a good user experience:
import { fetchState, derive } from '@anchorlib/core';
const state = fetchState([], { url: '/api/data' });
derive(state, (state) => {
switch (state.status) {
case 'pending':
// Show loading indicator
showSpinner();
break;
case 'success':
// Render data
renderData(state.data);
hideSpinner();
break;
case 'error':
// Show error message
showError(state.error?.message);
hideSpinner();
break;
}
});
2. Use Proper Initial Values
Initialize your fetch state with appropriate default values that match the expected response type:
// For object responses
const userState = fetchState<User | null>(null, { url: '/api/user' });
// For array responses
const usersState = fetchState<User[]>([], { url: '/api/users' });
// For primitive responses
const countState = fetchState<number>(0, { url: '/api/count' });
3. Handle Network Errors Gracefully
Network errors are common and should be handled gracefully:
import { fetchState, derive } from '@anchorlib/core';
const apiState = fetchState<Data | null>(null, { url: '/api/data' });
derive(apiState, (state) => {
if (state.status === 'error') {
// Log error for debugging
console.error('API Error:', state.error);
// Show user-friendly message
showUserFriendlyError('Unable to load data. Please try again later.');
}
});
4. Clean Up Subscriptions
When using [derive] with fetch states, remember to clean up subscriptions when they're no longer needed:
import { fetchState, derive } from '@anchorlib/core';
const state = fetchState([], { url: '/api/data' });
// Store the unsubscribe function
const unsubscribe = derive(state, (state) => {
// Handle state changes
});
// Call unsubscribe when component unmounts or when no longer needed
// unsubscribe();
5. Use Promise Conversion When Needed
For traditional async/await flows, use the promise conversion:
import { fetchState } from '@anchorlib/core';
async function loadUserData() {
const state = fetchState(null, { url: '/api/user' });
try {
const result = await fetchState.promise(state);
return result.data;
} catch (error) {
console.error('Failed to load user data:', error);
throw error;
}
}