From SWR FULL
SWR and data-query have a lot in common. A URL is the key, revalidation is declared, and the API stays small. Most of the translation is mechanical. The declaration sits outside the component, and writes belong to the same primitive instead of a separate hook.
The mechanical part
// SWR
const { data, error, isLoading } = useSWR('/api/articles', fetcher, {
revalidateOnFocus: true,
refreshInterval: 30000
});
// WildflowerJS
wildflower.query('articles', {
from: '/api/articles',
key: 'id',
refresh: ['focus', 30] // SECONDS, not milliseconds
});
<ul data-query="articles">
<template><li data-bind="title"></li></template>
</ul>
<p data-show="$articles.isLoading">Loading…</p>
Where SWR returns { data, error, isLoading } to a component, a query publishes the same state on a named entity that markup binds directly: $articles.rows, $articles.error, $articles.isLoading, plus isStale, lastSync, count, and pendingWrites.
| SWR | WildflowerJS | Notes |
|---|---|---|
| the key argument | from | A URL string, or a function. |
fetcher | nothing, or a function from | The engine fetches and parses JSON itself. |
global fetcher in SWRConfig | wildflower.config({ headers }) | Scoped by origin; 'self' is this page's own. |
revalidateOnFocus | 'focus' rung | |
revalidateOnReconnect | 'reconnect' rung | |
refreshInterval | a bare number rung | Seconds here, milliseconds there. |
dedupingInterval | 'fresh:N' rung | Gates event rungs rather than deduping calls. |
fallbackData | initial | |
keepPreviousData | always on | Rows are never blanked by a refresh; isStale marks the gap. |
conditional key (null) | an unresolved :token | Waits rather than fetching. Nothing to write. |
mutate() | write(), patch(), invalidate() | See below. |
Conditional fetching
SWR's idiom is a null key: useSWR(slug ? `/api/articles/${slug}` : null, fetcher). The equivalent here is to put the value in the URL and let the query wait for it.
wildflower.query('article', {
from: '/api/articles/:slug',
key: 'slug',
params: () => ({ slug: wildflower.getStore('route').slug })
});
Until route.slug has a value, nothing is requested and nothing is reported as an error. The query reports isLoading, since there is no data yet and nothing has failed, and it loads when the router sets the value and calls refresh().
Writes
SWR splits reading and writing. mutate() manipulates the cache, useSWRMutation handles remote writes, and the optimistic option asks you to supply both the optimistic data and the rollback behavior. Here the write side is part of the same declaration.
// SWR
await mutate('/api/articles', updateArticle(article), {
optimisticData: (current) => current.map(a => a.id === article.id ? article : a),
rollbackOnError: true,
revalidate: true
});
// WildflowerJS
wildflower.query('articles', {
from: '/api/articles',
key: 'id',
to: '/api/articles/:id',
body: item => item
});
wildflower.getQuery('articles').write({ id: 42, title: 'Renamed' });
You do not write the optimistic projection because the key and the field merge determine it, and rollback is field-level rather than whole-list: only the fields that write still owns revert, so a second edit in flight on the same row survives the first one's failure.
mutate() used purely as a local cache write, with no request, maps to patch(). mutate() used as "just revalidate" maps to invalidate().
Structural differences
Declarations are global. SWR queries are declared inside the component that uses them, and two components asking for the same key share one cache entry automatically. Here you declare a query once, by name, and any number of elements bind it. That is less ceremony for shared data and less convenient for a one-off fetch that belongs to a single component, where a plain fetch() in init() is still the right tool.
The cache is not addressable. SWR keys by URL, and so does a query's result cache, so switching back to a URL you have already loaded paints from memory in both. What SWR gives you on top is the key itself. mutate(key) can rewrite or revalidate any entry from anywhere, and every entry stays live at once. A query's cache is a repaint source for one result set rather than a set of addressable entries, so two views needing different slices at the same time are two named queries. For stacking pages, append accumulates instead.