@flight-framework/data
v0.0.6
Published
Data fetching primitives for Flight Framework - loaders, actions, forms, useFetch, and useAsyncData
Maintainers
Readme
@flight-framework/data
Data fetching primitives for Flight Framework.
Installation
npm install @flight-framework/dataLoaders
Loaders run on the server before rendering a page. They fetch data that the page needs.
// src/routes/posts/[slug].page.tsx
import { useLoaderData } from '@flight-framework/data';
export async function loader({ params }: LoaderContext) {
const post = await db.posts.findUnique({
where: { slug: params.slug }
});
if (!post) {
throw new Response('Not Found', { status: 404 });
}
return { post };
}
export default function PostPage() {
const { post } = useLoaderData<typeof loader>();
return (
<article>
<h1>{post.title}</h1>
<div>{post.content}</div>
</article>
);
}Actions
Actions handle form submissions and mutations. They run on the server when a form is submitted.
import { redirect, Form } from '@flight-framework/data';
export async function action({ request }: ActionContext) {
const formData = await request.formData();
await db.posts.create({
data: {
title: formData.get('title') as string,
content: formData.get('content') as string,
}
});
return redirect('/posts');
}
export default function NewPostPage() {
return (
<Form method="post">
<input name="title" placeholder="Title" required />
<textarea name="content" placeholder="Content" />
<button type="submit">Create Post</button>
</Form>
);
}useFetcher
For data fetching without navigation:
import { useFetcher } from '@flight-framework/data';
function SearchBox() {
const fetcher = useFetcher<SearchResult[]>();
return (
<div>
<input
type="search"
onChange={(e) => fetcher.load(`/api/search?q=${e.target.value}`)}
/>
{fetcher.state === 'loading' && <Spinner />}
{fetcher.data?.map(result => (
<SearchResultItem key={result.id} result={result} />
))}
</div>
);
}Response Utilities
import { redirect, json, notFound, badRequest } from '@flight-framework/data';
// Redirect
return redirect('/dashboard');
// JSON response
return json({ success: true });
// 404 Not Found
return notFound();
// 400 Bad Request
return badRequest({ error: 'Invalid input' });Type Safety
The SerializeFrom utility type extracts the return type of a loader:
import type { SerializeFrom } from '@flight-framework/data';
export async function loader() {
return { posts: await getPosts() };
}
// Type is: { posts: Post[] }
type LoaderData = SerializeFrom<typeof loader>;SSR Integration
Loader data is automatically hydrated from SSR to client:
// entry-server.tsx
import { hydrateLoaderData } from '@flight-framework/data';
const html = `
<html>
<body>
<div id="root">${content}</div>
${hydrateLoaderData(url, loaderData)}
</body>
</html>
`;Client-Side Data Fetching
For client-side data fetching with caching and deduplication, Flight provides useFetch and useAsyncData.
useFetch
Simple, cached data fetching:
// React
import { useFetch } from '@flight-framework/data/react';
function Users() {
const { data, pending, error, refresh } = useFetch<User[]>('/api/users');
if (pending) return <p>Loading...</p>;
if (error) return <p>Error: {error.message}</p>;
return (
<div>
<button onClick={refresh}>Refresh</button>
<ul>{data.map(u => <li key={u.id}>{u.name}</li>)}</ul>
</div>
);
}Framework Adapters
Import from the adapter for your framework:
// React
import { useFetch, useAsyncData } from '@flight-framework/data/react';
// Vue
import { useFetch, useAsyncData } from '@flight-framework/data/vue';
// Svelte
import { useFetch, useAsyncData } from '@flight-framework/data/svelte';
// Solid
import { useFetch, useAsyncData } from '@flight-framework/data/solid';useAsyncData
For custom fetching logic:
import { useAsyncData } from '@flight-framework/data/react';
function Analytics() {
const { data, pending, execute } = useAsyncData(
'analytics-weekly',
async () => await analyticsAPI.getWeeklyReport()
);
return (
<div>
<button onClick={execute}>Refresh</button>
{pending ? <Spinner /> : <Chart data={data} />}
</div>
);
}Options
useFetch('/api/data', {
key: 'custom-key', // Cache key (default: url)
immediate: true, // Fetch on mount (default: true)
default: [], // Default value while loading
cache: true, // Enable caching (default: true)
dedupe: true, // Deduplicate requests (default: true)
maxAge: 60000, // Cache TTL in ms
revalidateOnFocus: true // Re-fetch on window focus
});Cache Utilities
import { invalidate, prefetch, clearCache } from '@flight-framework/data';
// Invalidate specific key
invalidate('/api/users');
// Invalidate by pattern
invalidate(key => key.startsWith('/api/'));
// Prefetch data
await prefetch('/api/users');
// Clear all cache
clearCache();Features
| Feature | Description | |---------|-------------| | LRU Cache | O(1) operations, memory-bounded | | Deduplication | Multiple calls share one request | | Stale-While-Revalidate | Instant stale data, background refresh | | SSR Hydration | Seamless server-to-client transfer | | Framework Agnostic | React, Vue, Svelte, Solid |
License
MIT
