import{_ as i,o as a,c as n,ae as h}from"./chunks/framework.C8AWLET_.js";const d=JSON.parse('{"title":"Fetch API 🌐","description":"","frontmatter":{},"headers":[],"relativePath":"api/fetch.md","filePath":"api/fetch.md"}'),l={name:"api/fetch.md"};function k(t,s,p,e,E,F){return a(),n("div",null,[...s[0]||(s[0]=[h(`
SigPro provides a simple, lightweight wrapper around the native Fetch API that integrates seamlessly with signals for loading state management. It's designed for common use cases with sensible defaults.
$.fetch? A ultra-simple fetch wrapper that:
null on error (no try/catch needed for basic usage)$.fetch(url, data, [loading]) Makes a POST request with JSON data and optional loading signal.
import { $ } from 'sigpro';
const loading = $(false);
async function loadUser() {
const user = await $.fetch('/api/user', { id: 123 }, loading);
if (user) {
console.log('User loaded:', user);
}
}| Parameter | Type | Description |
|---|---|---|
url | string | Endpoint URL |
data | Object | Data to send (automatically JSON.stringify'd) |
loading | Function (optional) | Signal function to track loading state |
| Return | Description |
|---|---|
Promise<Object|null> | Parsed JSON response or null on error |
import { $ } from 'sigpro';
const userData = $(null);
async function fetchUser(id) {
const data = await $.fetch('/api/user', { id });
if (data) {
userData(data);
}
}
fetchUser(123);import { $, html } from 'sigpro';
const user = $(null);
const loading = $(false);
async function loadUser(id) {
const data = await $.fetch('/api/user', { id }, loading);
if (data) user(data);
}
// In your template
html\`
<div>
\${() => loading() ? html\`
<div class="spinner">Loading...</div>
\` : user() ? html\`
<div>
<h2>\${user().name}</h2>
<p>Email: \${user().email}</p>
</div>
\` : html\`
<p>No user found</p>
\`}
</div>
\`;import { $ } from 'sigpro';
const userId = $(1);
const user = $(null);
const loading = $(false);
$.effect(() => {
const id = userId();
if (id) {
$.fetch(\`/api/users/\${id}\`, null, loading).then(data => {
if (data) user(data);
});
}
});
userId(2); // Automatically fetches new userimport { $, html } from 'sigpro';
const Profile = () => {
const userId = $(1);
const user = $(null);
const loading = $(false);
const error = $(null);
const fetchUser = async (id) => {
error(null);
const data = await $.fetch('/api/user', { id }, loading);
if (data) {
user(data);
} else {
error('Failed to load user');
}
};
// Fetch when userId changes
$.effect(() => {
fetchUser(userId());
});
return html\`
<div class="profile">
<div class="user-selector">
<button @click=\${() => userId(1)}>User 1</button>
<button @click=\${() => userId(2)}>User 2</button>
<button @click=\${() => userId(3)}>User 3</button>
</div>
\${() => {
if (loading()) {
return html\`<div class="spinner">Loading profile...</div>\`;
}
if (error()) {
return html\`<div class="error">\${error()}</div>\`;
}
if (user()) {
return html\`
<div class="user-info">
<h2>\${user().name}</h2>
<p>Email: \${user().email}</p>
<p>Role: \${user().role}</p>
<p>Joined: \${new Date(user().joined).toLocaleDateString()}</p>
</div>
\`;
}
return html\`<p>Select a user</p>\`;
}}
</div>
\`;
};import { $, html } from 'sigpro';
const TodoApp = () => {
const todos = $([]);
const loading = $(false);
const newTodo = $('');
const filter = $('all'); // 'all', 'active', 'completed'
// Load todos
const loadTodos = async () => {
const data = await $.fetch('/api/todos', {}, loading);
if (data) todos(data);
};
// Add todo
const addTodo = async () => {
if (!newTodo().trim()) return;
const todo = await $.fetch('/api/todos', {
text: newTodo(),
completed: false
});
if (todo) {
todos([...todos(), todo]);
newTodo('');
}
};
// Toggle todo
const toggleTodo = async (id, completed) => {
const updated = await $.fetch(\`/api/todos/\${id}\`, {
completed: !completed
});
if (updated) {
todos(todos().map(t =>
t.id === id ? updated : t
));
}
};
// Delete todo
const deleteTodo = async (id) => {
const result = await $.fetch(\`/api/todos/\${id}/delete\`, {});
if (result) {
todos(todos().filter(t => t.id !== id));
}
};
// Filtered todos
const filteredTodos = $(() => {
const currentFilter = filter();
if (currentFilter === 'all') return todos();
if (currentFilter === 'active') {
return todos().filter(t => !t.completed);
}
return todos().filter(t => t.completed);
});
// Load on mount
loadTodos();
return html\`
<div class="todo-app">
<h1>Todo List</h1>
<div class="add-todo">
<input
type="text"
:value=\${newTodo}
@keydown.enter=\${addTodo}
placeholder="Add a new todo..."
/>
<button @click=\${addTodo}>Add</button>
</div>
<div class="filters">
<button
class:active=\${() => filter() === 'all'}
@click=\${() => filter('all')}
>
All
</button>
<button
class:active=\${() => filter() === 'active'}
@click=\${() => filter('active')}
>
Active
</button>
<button
class:active=\${() => filter() === 'completed'}
@click=\${() => filter('completed')}
>
Completed
</button>
</div>
\${() => loading() ? html\`
<div class="spinner">Loading todos...</div>
) : html\`
<ul class="todo-list">
\${filteredTodos().map(todo => html\`
<li class="todo-item">
<input
type="checkbox"
:checked=\${todo.completed}
@change=\${() => toggleTodo(todo.id, todo.completed)}
/>
<span class:completed=\${todo.completed}>\${todo.text}</span>
<button @click=\${() => deleteTodo(todo.id)}>🗑️</button>
</li>
\`)}
</ul>
\`}
</div>
\`;
};import { $, html } from 'sigpro';
const InfiniteScroll = () => {
const posts = $([]);
const page = $(1);
const loading = $(false);
const hasMore = $(true);
const error = $(null);
const loadMore = async () => {
if (loading() || !hasMore()) return;
const data = await $.fetch('/api/posts', {
page: page(),
limit: 10
}, loading);
if (data) {
if (data.posts.length === 0) {
hasMore(false);
} else {
posts([...posts(), ...data.posts]);
page(p => p + 1);
}
} else {
error('Failed to load posts');
}
};
// Intersection Observer for infinite scroll
$.effect(() => {
const observer = new IntersectionObserver(
(entries) => {
if (entries[0].isIntersecting) {
loadMore();
}
},
{ threshold: 0.1 }
);
const sentinel = document.getElementById('sentinel');
if (sentinel) observer.observe(sentinel);
return () => observer.disconnect();
});
// Initial load
loadMore();
return html\`
<div class="infinite-scroll">
<h1>Posts</h1>
<div class="posts">
\${posts().map(post => html\`
<article class="post">
<h2>\${post.title}</h2>
<p>\${post.body}</p>
<small>By \${post.author}</small>
</article>
\`)}
</div>
<div id="sentinel" class="sentinel">
\${() => {
if (loading()) {
return html\`<div class="spinner">Loading more...</div>\`;
}
if (error()) {
return html\`<div class="error">\${error()}</div>\`;
}
if (!hasMore()) {
return html\`<div class="end">No more posts</div>\`;
}
return '';
}}
</div>
</div>
\`;
};import { $, html } from 'sigpro';
const SearchComponent = () => {
const query = $('');
const results = $([]);
const loading = $(false);
const error = $(null);
let searchTimeout;
const performSearch = async (searchQuery) => {
if (!searchQuery.trim()) {
results([]);
return;
}
const data = await $.fetch('/api/search', {
q: searchQuery
}, loading);
if (data) {
results(data);
} else {
error('Search failed');
}
};
// Debounced search
$.effect(() => {
const searchQuery = query();
clearTimeout(searchTimeout);
if (searchQuery.length < 2) {
results([]);
return;
}
searchTimeout = setTimeout(() => {
performSearch(searchQuery);
}, 300);
return () => clearTimeout(searchTimeout);
});
return html\`
<div class="search">
<div class="search-box">
<input
type="search"
:value=\${query}
placeholder="Search..."
class="search-input"
/>
\${() => loading() ? html\`
<span class="spinner-small">⌛</span>
) : ''}
</div>
\${() => {
if (error()) {
return html\`<div class="error">\${error()}</div>\`;
}
if (results().length > 0) {
return html\`
<ul class="results">
\${results().map(item => html\`
<li class="result-item">
<h3>\${item.title}</h3>
<p>\${item.description}</p>
</li>
\`)}
</ul>
\`;
}
if (query().length >= 2 && !loading()) {
return html\`<p class="no-results">No results found</p>\`;
}
return '';
}}
</div>
\`;
};import { $, html } from 'sigpro';
const ContactForm = () => {
const formData = $({
name: '',
email: '',
message: ''
});
const submitting = $(false);
const submitError = $(null);
const submitSuccess = $(false);
const handleSubmit = async (e) => {
e.preventDefault();
submitError(null);
submitSuccess(false);
const result = await $.fetch('/api/contact', formData(), submitting);
if (result) {
submitSuccess(true);
formData({ name: '', email: '', message: '' });
} else {
submitError('Failed to send message. Please try again.');
}
};
const updateField = (field, value) => {
formData({
...formData(),
[field]: value
});
};
return html\`
<form class="contact-form" @submit=\${handleSubmit}>
<h2>Contact Us</h2>
<div class="form-group">
<label for="name">Name:</label>
<input
type="text"
id="name"
:value=\${() => formData().name}
@input=\${(e) => updateField('name', e.target.value)}
required
?disabled=\${submitting}
/>
</div>
<div class="form-group">
<label for="email">Email:</label>
<input
type="email"
id="email"
:value=\${() => formData().email}
@input=\${(e) => updateField('email', e.target.value)}
required
?disabled=\${submitting}
/>
</div>
<div class="form-group">
<label for="message">Message:</label>
<textarea
id="message"
:value=\${() => formData().message}
@input=\${(e) => updateField('message', e.target.value)}
required
rows="5"
?disabled=\${submitting}
></textarea>
</div>
\${() => {
if (submitting()) {
return html\`<div class="submitting">Sending...</div>\`;
}
if (submitError()) {
return html\`<div class="error">\${submitError()}</div>\`;
}
if (submitSuccess()) {
return html\`<div class="success">Message sent successfully!</div>\`;
}
return '';
}}
<button
type="submit"
?disabled=\${submitting}
>
Send Message
</button>
</form>
\`;
};import { $, html } from 'sigpro';
const Dashboard = () => {
// Multiple data streams
const metrics = $({});
const alerts = $([]);
const logs = $([]);
const loading = $({
metrics: false,
alerts: false,
logs: false
});
const refreshInterval = $(5000); // 5 seconds
const fetchMetrics = async () => {
const data = await $.fetch('/api/metrics', {}, loading().metrics);
if (data) metrics(data);
};
const fetchAlerts = async () => {
const data = await $.fetch('/api/alerts', {}, loading().alerts);
if (data) alerts(data);
};
const fetchLogs = async () => {
const data = await $.fetch('/api/logs', {
limit: 50
}, loading().logs);
if (data) logs(data);
};
// Auto-refresh all data
$.effect(() => {
fetchMetrics();
fetchAlerts();
fetchLogs();
const interval = setInterval(() => {
fetchMetrics();
fetchAlerts();
}, refreshInterval());
return () => clearInterval(interval);
});
return html\`
<div class="dashboard">
<header>
<h1>System Dashboard</h1>
<div class="refresh-control">
<label>
Refresh interval:
<select :value=\${refreshInterval} @change=\${(e) => refreshInterval(parseInt(e.target.value))}>
<option value="2000">2 seconds</option>
<option value="5000">5 seconds</option>
<option value="10000">10 seconds</option>
<option value="30000">30 seconds</option>
</select>
</label>
</div>
</header>
<div class="dashboard-grid">
<!-- Metrics Panel -->
<div class="panel metrics">
<h2>System Metrics</h2>
\${() => loading().metrics ? html\`
<div class="spinner">Loading metrics...</div>
) : html\`
<div class="metrics-grid">
<div class="metric">
<label>CPU</label>
<span>\${metrics().cpu || 0}%</span>
</div>
<div class="metric">
<label>Memory</label>
<span>\${metrics().memory || 0}%</span>
</div>
<div class="metric">
<label>Requests</label>
<span>\${metrics().requests || 0}/s</span>
</div>
</div>
\`}
</div>
<!-- Alerts Panel -->
<div class="panel alerts">
<h2>Active Alerts</h2>
\${() => loading().alerts ? html\`
<div class="spinner">Loading alerts...</div>
) : alerts().length > 0 ? html\`
<ul>
\${alerts().map(alert => html\`
<li class="alert \${alert.severity}">
<strong>\${alert.type}</strong>
<p>\${alert.message}</p>
<small>\${new Date(alert.timestamp).toLocaleTimeString()}</small>
</li>
\`)}
</ul>
) : html\`
<p class="no-data">No active alerts</p>
\`}
</div>
<!-- Logs Panel -->
<div class="panel logs">
<h2>Recent Logs</h2>
\${() => loading().logs ? html\`
<div class="spinner">Loading logs...</div>
) : html\`
<ul>
\${logs().map(log => html\`
<li class="log \${log.level}">
<span class="timestamp">\${new Date(log.timestamp).toLocaleTimeString()}</span>
<span class="message">\${log.message}</span>
</li>
\`)}
</ul>
\`}
</div>
</div>
</div>
\`;
};import { $, html } from 'sigpro';
const FileUploader = () => {
const files = $([]);
const uploading = $(false);
const uploadProgress = $({});
const uploadResults = $([]);
const handleFileSelect = (e) => {
files([...e.target.files]);
};
const uploadFiles = async () => {
if (files().length === 0) return;
uploading(true);
uploadResults([]);
for (const file of files()) {
const formData = new FormData();
formData.append('file', file);
// Track progress for this file
uploadProgress({
...uploadProgress(),
[file.name]: 0
});
try {
// Custom fetch for FormData
const response = await fetch('/api/upload', {
method: 'POST',
body: formData
});
const result = await response.json();
uploadResults([
...uploadResults(),
{ file: file.name, success: true, result }
]);
} catch (error) {
uploadResults([
...uploadResults(),
{ file: file.name, success: false, error: error.message }
]);
}
uploadProgress({
...uploadProgress(),
[file.name]: 100
});
}
uploading(false);
};
return html\`
<div class="file-uploader">
<h2>Upload Files</h2>
<input
type="file"
multiple
@change=\${handleFileSelect}
?disabled=\${uploading}
/>
\${() => files().length > 0 ? html\`
<div class="file-list">
<h3>Selected Files:</h3>
<ul>
\${files().map(file => html\`
<li>
\${file.name} (\${(file.size / 1024).toFixed(2)} KB)
\${() => uploadProgress()[file.name] ? html\`
<progress value="\${uploadProgress()[file.name]}" max="100"></progress>
) : ''}
</li>
\`)}
</ul>
<button
@click=\${uploadFiles}
?disabled=\${uploading}
>
\${() => uploading() ? 'Uploading...' : 'Upload Files'}
</button>
</div>
\` : ''}
\${() => uploadResults().length > 0 ? html\`
<div class="upload-results">
<h3>Upload Results:</h3>
<ul>
\${uploadResults().map(result => html\`
<li class="\${result.success ? 'success' : 'error'}">
\${result.file}:
\${result.success ? 'Uploaded successfully' : \`Failed: \${result.error}\`}
</li>
\`)}
</ul>
</div>
\` : ''}
</div>
\`;
};import { $ } from 'sigpro';
// Enhanced fetch with retry
const fetchWithRetry = async (url, data, loading, maxRetries = 3) => {
let lastError;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
if (loading) loading(true);
const result = await $.fetch(url, data);
if (result !== null) {
return result;
}
// If we get null but no error, wait and retry
if (attempt < maxRetries) {
await new Promise(resolve =>
setTimeout(resolve, Math.pow(2, attempt) * 1000) // Exponential backoff
);
}
} catch (error) {
lastError = error;
console.warn(\`Attempt \${attempt} failed:\`, error);
if (attempt < maxRetries) {
await new Promise(resolve =>
setTimeout(resolve, Math.pow(2, attempt) * 1000)
);
}
} finally {
if (attempt === maxRetries && loading) {
loading(false);
}
}
}
console.error('All retry attempts failed:', lastError);
return null;
};
// Usage
const loading = $(false);
const data = await fetchWithRetry('/api/unreliable-endpoint', {}, loading, 5);// ❌ Don't assume success
const data = await $.fetch('/api/data');
console.log(data.property); // Might throw if data is null
// ✅ Check for null
const data = await $.fetch('/api/data');
if (data) {
console.log(data.property);
} else {
showError('Failed to load data');
}// ❌ Manual fetching
button.addEventListener('click', async () => {
const data = await $.fetch('/api/data');
updateUI(data);
});
// ✅ Reactive fetching
const trigger = $(false);
$.effect(() => {
if (trigger()) {
$.fetch('/api/data').then(data => {
if (data) updateUI(data);
});
}
});
trigger(true); // Triggers fetch// ✅ Always show loading state
const loading = $(false);
const data = $(null);
async function load() {
const result = await $.fetch('/api/data', {}, loading);
if (result) data(result);
}
// In template
html\`
<div>
\${() => loading() ? '<Spinner />' :
data() ? '<Data />' :
'<Empty />'}
</div>
\`;// ✅ Use AbortController with effects
let controller;
$.effect(() => {
if (controller) {
controller.abort();
}
controller = new AbortController();
fetch(url, { signal: controller.signal })
.then(res => res.json())
.then(data => {
if (!controller.signal.aborted) {
updateData(data);
}
});
return () => controller.abort();
});const data = await $.fetch('/api/data');
if (!data) {
// Handle error (show message, retry, etc.)
}const data = $(null);
const error = $(null);
const loading = $(false);
async function loadData() {
error(null);
const result = await $.fetch('/api/data', {}, loading);
if (result) {
data(result);
} else {
error('Failed to load data');
}
}`,54)])])}const g=i(l,[["render",k]]);export{d as __pageData,g as default};Pro Tip: Combine
$.fetchwith$.effectand loading signals for a complete reactive data fetching solution. The loading signal integration makes it trivial to show loading states in your UI.