3 min read
Data Fetching Example
Fetch data automatically when conditions are met.
The Module
import { createModule, createSystem, t } from '@directive-run/core';
interface User {
id: number;
name: string;
email: string;
}
const userModule = createModule("user", {
schema: {
// Facts hold the current state of the module
facts: {
userId: t.number(),
user: t.object<User>().nullable(),
loading: t.boolean(),
error: t.string().nullable(),
},
// Typed requirement payloads –ensures resolvers receive the right shape
requirements: {
FETCH_USER: t.object<{ userId: number }>(),
},
},
// Nothing loaded yet –userId of 0 means "no selection"
init: (facts) => {
facts.userId = 0;
facts.user = null;
facts.loading = false;
facts.error = null;
},
constraints: {
// Fires when we have a userId but no user data and nothing in-flight
needsUser: {
when: (facts) =>
facts.userId > 0 &&
facts.user === null &&
!facts.loading &&
facts.error === null,
// Dynamic requirement –passes the current userId to the resolver
require: (facts) => ({
type: "FETCH_USER",
userId: facts.userId,
}),
},
},
resolvers: {
fetchUser: {
requirement: "FETCH_USER",
// Automatically retry failed requests with exponential backoff
retry: { attempts: 3, backoff: "exponential" },
// Abort if the request takes longer than 10 seconds
timeout: 10000,
resolve: async (req, context) => {
// Signal loading state so the constraint won't re-fire
context.facts.loading = true;
context.facts.error = null;
try {
const response = await fetch(`/api/users/${req.userId}`);
if (!response.ok) {
throw new Error("Failed to fetch user");
}
context.facts.user = await response.json();
} catch (error) {
context.facts.error = error.message;
} finally {
context.facts.loading = false;
}
},
},
},
});
// Create and start –the constraint is now watching for userId changes
const system = createSystem({ module: userModule });
system.start();
React Component
import { useFact } from '@directive-run/react';
function UserProfile() {
// Subscribe to each fact individually –only re-renders when that fact changes
const userId = useFact(system, 'userId');
const user = useFact(system, 'user');
const loading = useFact(system, 'loading');
const error = useFact(system, 'error');
// --- Loading state ---
if (loading) {
return <div>Loading...</div>;
}
// --- Error state: clearing the error re-activates the constraint to retry ---
if (error) {
return (
<div>
<p>Error: {error}</p>
<button onClick={() => { system.facts.error = null }}>
Retry
</button>
</div>
);
}
// --- Empty state: setting userId triggers the fetch automatically ---
if (!user) {
return (
<div>
<input
type="number"
placeholder="Enter user ID"
onChange={(e) => { system.facts.userId = Number(e.target.value) }}
/>
</div>
);
}
// --- Success state: user data has been fetched and is available ---
return (
<div>
<h1>{user.name}</h1>
<p>{user.email}</p>
</div>
);
}
How It Works
- User enters ID in input
facts.userIdis set- Constraint
needsUserbecomes active - Requirement
FETCH_USERis raised - Resolver fetches data with retries
- Component re-renders with user data
Next Steps
- Form Validation – Input handling
- Constraints – More patterns
- Resolvers – Retry configuration

