Releases: vercel/swr
2.0.0-rc.0
The SWR 2.0 release candidate. This RC has one breaking change, a new option for mutation APIs, and some bug fixes & improvements.
Highlights & Breakings
A failed mutation will not cause useSWR
's error
to be updated:
const { error, mutate } = useSWR('/api/user', getUser)
return <button onClick={async () => {
try {
await mutate(updateUser)
} catch (mutationError) {
// `mutationError` will not cause `error` to be updated.
}
}}>Update User</button>
In 2.0, with the example above, error
will only be coming from getUser
and will be shared across all useSWR('/api/user')
hooks. And mutation errors (mutationError
) will be coming from mutations like updateUser
calls, they will be separated from fetcher errors.
There is also a new option throwOnError
for useSWRMutation
to disable the default throwing behavior of trigger
:
const { trigger } = useSWRMutation('/api/user', updateUser)
try {
await trigger()
} catch (err) {
// ... it throws when failed to trigger the mutation so you can
// easily change the flow here
}
const { trigger, error } = useSWRMutation('/api/user', updateUser, {
throwOnError: false
})
// You don't need to try-catch here, you can instead handle errors
// on the component level in a declarative way
await trigger()
Read more about this change in #2182.
What's Changed
- chore: fix missing husky warning by @huozhi in #2160
- fix: Remove downlevelIteration and traverse iterator manually by @huozhi in #2181
- fix: re-render when returned data and fallbackData is the same and keepPreviousData is enabled by @koba04 in #2169
- breaking: Change the error broadcasting behavior in mutations and add
throwOnError
option by @shuding in #2182 - docs: Add JSDoc comments to useSWRMutation by @shuding in #2183
Full Changelog: 2.0.0-beta.7...2.0.0-rc.0
2.0.0-beta.7
Most changes in this release are maintenance related, as we are finalizing everything for the upcoming 2.0 stable version.
What's Changed
- Upgrading use-sync-external-store lib by @sanjaiyan-dev in #2063
- Revert swr config generic by @huozhi in #2065
- Fix: raf return type by @huozhi in #2074
- Remove unused info from dummy package.json by @huozhi in #2083
- Use pnpm workspace by @huozhi in #2085
- bump typescript and apply nodenext module resolution by @huozhi in #2084
- Chore: Typos and grammar by @AllanOliveiraM in #2094
- chore: add basic tsdoc by @promer94 in #2089
- types: add generic for SWRInfiniteKeyLoader by @jason89521 in #2104
- fix: add swr/_internal to paths by @jason89521 in #2105
- type: make tsconfig simpler and fix type error by @promer94 in #2115
- chore: exclude .tsbuildinfo from files, reduce install size by @promer94 in #2119
- Reorder exports conditions by @huozhi in #2130
- Upgrade bunchee by @huozhi in #2132
- Bump typescript and assemble pnpm scripts by @huozhi in #2133
- chore: add github actions reporter by @promer94 in #2131
- Dedupe installed swc by @huozhi in #2136
- test: add a test for preload API in effects by @koba04 in #2138
- test: add more isLoading tests for the case a key is null or an error by @koba04 in #2141
- ci: use action/setup-node built-in pnpm cache and bump to node16 by @promer94 in #2142
- test: add tests for dependency collection by @koba04 in #2140
- chore: bump dev deps by @huozhi in #2143
- memoize config values to avoid frequent context propagation by @gnoff in #2149
- Bundle types into one file by @huozhi in #2150
- Revert target option by @huozhi in #2151
New Contributors
- @sanjaiyan-dev made their first contribution in #2063
- @AllanOliveiraM made their first contribution in #2094
- @jason89521 made their first contribution in #2104
- @gnoff made their first contribution in #2149
Full Changelog: 2.0.0-beta.6...2.0.0-beta.7
2.0.0-beta.6
2.0.0-beta.5
Highlights & Breakings
Mutate Multiple Keys (#1946, #1989)
You can now pass a filter function to the global mutate
API to match any keys and mutate them together:
import { mutate } from 'swr'
// Or from the hook if you customized the cache provider:
// { mutate } = useSWRConfig()
mutate(
key => typeof key === 'string' && key.startsWith('/api/item?id='),
data => update(data),
true
)
This action will match all keys starting with '/api/item?id='
, and replace their data with update
, then re-fetch after the mutation. The signature is the same as the current mutate API:
mutate(
'/api/item?id=123',
data => update(data),
true
)
The only difference is if you pass a function instead of a specific key, SWR will use it to match and mutate all the data in the cache. It will be convenient to use this to batch updates, or mutate keys by pattern.
Worth noting that it works with any key types, too:
useSWR(['item', 123], ...)
useSWR(['item', 124], ...)
useSWR(['item', 125], ...)
mutate(
key => Array.isArray(key) && key[0] === 'item',
undefined,
false
)
The mutation above will match all 3 keys and set the values to undefined (clear them), and skip the revalidation at the end. So another technique is to clear everything with this (e.g. when logging out):
mutate(
() => true,
undefined,
false
)
More use cases and discussions can be found in the original RFC: #1946.
What's Changed
- feat: Mutate multiple keys by @huozhi in #1989
- fix: Avoid preloading the resource multiple times by @shuding in #2036
- fix: isLoading and isValidating should always respect cache value by @promer94 in #2048
- chore: Fix TS type generation by @huozhi in #2038
Full Changelog: 2.0.0-beta.4...2.0.0-beta.5
2.0.0-beta.4
Highlights
Preload API (#2026)
SWR now has a preload
API that you can call programmatically to kick off the request early. For example, you can do preload('/api/user', fetcher)
even outside of React:
import { useState } from 'react'
import useSWR, { preload } from 'swr'
const fetcher = (url) => fetch(url).then((res) => res.json())
// Preload the resource before rendering the User component below,
// this prevents potential waterfalls in your application.
// You can also start preloading when hovering the button or link, too.
preload('/api/user', fetcher)
function User() {
const { data } = useSWR('/api/user', fetcher)
...
}
export default function App() {
const [show, setShow] = useState(false)
return (
<div>
<button onClick={() => setShow(true)}>Show User</button>
{show ? <User /> : null}
</div>
)
}
So at the moment of clicking the button and actually rendering the User
component, the resource is likely loaded already. If the request depends on some props or states, you can also preload it when hovering the button:
function App({ userId }) {
const [show, setShow] = useState(false)
return (
<div>
<button
onClick={() => setShow(true)}
onHover={() => preload('/api/user?id=' + userId, fetcher)}
>
Show User
</button>
{show ? <User /> : null}
</div>
)
}
Demo: https://codesandbox.io/s/swr-preloading-14bikv?file=/src/App.js
Function as SWRConfig
value (#2024)
A new way to extend the SWR global configuration:
<SWRConfig value={{ revalidateOnFocus: false, dedupingInterval: 5000 }}>
<div>
<Header/>
<SWRConfig value={config => ({ ...config, dedupingInterval: 1000 })}>
<Main />
</SWRConfig>
</div>
</SWRConfig>
Where you can inherit the parent configuration and override the dedupingInterval
value, but reuse the other options.
Breakings
SWRConfig.default
→ SWRConfig.defaultValue
(#2023)
This is a currently undocumented API, but planned to go stable with the 2.0 release. You can access to SWR’s default options via the SWRConfig.defaultValue
static and read-only property.
What's Changed
- breaking: Rename SWRConfig.default to SWRConfig.defaultValue by @huozhi in #2023
- feat: Add preload function by @koba04 in #2026
- feat: Support function as context config by @huozhi in #2024
- fix: Should always reset to the original data after mutating with optimistic data by @shuding in #1982
- fix: Should only update cache when actually start new request by @promer94 in #1986
- fix: useSWRMutation - clear error after successful trigger (#1973) by @anirudh1713 in #1995
- chore: Adding debounce for autocomplete-suggestions example by @Walkeryr in #2017
- chore: Remove useless ref copy by @promer94 in #1981
- chore: Switch to pnpm and turborepo by @promer94 in #1983
- test: Add test cases for mutate by @shuding in #1976
- chore: Save some bytes by @promer94 in #1991
- chore: getSWRCacahe -> getSWRCache by @sdornan in #2000
- chore: Do not abort on watch mode when ts erroring by @huozhi in #1992
- chore: Add engine field to limit pnpm version by @huozhi in #1990
- test: Add test case for #1974 by @promer94 in #2005
- test: Add test cases for mutate by @shuding in #1976
- test: Fix all act warnings by @koba04 in #2031
New Contributors
Full Changelog: 2.0.0-beta.3...2.0.0-beta.4
2.0.0-beta.3
Highlights
Better React 18 Support (#1962)
This is a change of SWR's internal implementation detail. For developers that use SWR, it will just work out of the box without any changes in their apps.
Brought to you by @promer94 and @shuding, this release includes a core refactoring that improves React 18 support by adopting APIs like useSyncExternalStore
and startTransition
internally. Especially when rendering UIs concurrently with React 18, this new SWR version ensures stronger UI consistency.
Worth note that the current stable 1.x version of SWR still works well in React 18.
This core change isn't breaking and does not affect React <=17 apps.
Breakings
Avoid using Suspense on the server-side (#1931)
When using suspense: true
with SWR on the server-side (including pre-rendering in Next.js), it's now required to provide the initial data via fallbackData
or fallback
. This means that you can't use Suspense to fetch data on the server side as of today, but either doing fully client-side data fetching, or fetch the data via the framework (such as getStaticProps
in Next.js).
While Suspense for libraries is still experimental, this behavior might change before the 2.0 stable release. More discussions can be found here: #1906.
What's Changed
- breaking: Throw error when using suspense on the server side without fallback in React 18 by @shuding in #1931
- feat: Improved React 18 support by @promer94 in #1962
- fix: Fix cache types by @chibicode in #1961
- fix: Remove mount check for react18 by @promer94 in #1927
- fix: Mutate args for useSWRInfinite hook by @sanjeev29 in #1947
- fix: Import with-selector with extension from useESE by @huozhi in #1967
- fix: Bug fixes by @shuding in #1968
- fix: Fix race conditions related to optimistic UI by @shuding in #1970
- type: Extends useConfig cache interface by @promer94 in #1938
- type: Fix type regressions by @promer94 in #1966
- chore: Don't check unused vars with underscore prefix by @huozhi in #1939
- chore: Upgrade to jest 28 by @huozhi in #1942
- chore: Faster type checking by @shuding in #1969
- chore: Fix codesanbox ci import error by @promer94 in #1971
New Contributors
- @chibicode made their first contribution in #1961
- @sanjeev29 made their first contribution in #1947
Full Changelog: 2.0.0-beta.1...2.0.0-beta.3
2.0.0-beta.1
SWR 2.0 on its way! Check https://github.com/vercel/swr/releases/tag/2.0.0-beta.0 for the previous 2.0 beta updates.
💖 Give feedback in discussion: #1932.
Highlights
New isLoading
state (#1928)
Previously, useSWR
only returns a isValidating
state, which is an indicator of both initial requests and automatic & manual revalidations. It includes polling requests and focus revalidations, etc.
But if you need to display an initial skeleton while loading the data, you will have to do something like
const isLoading = typeof data === 'undefined' && !error
...which is a popular pattern in the community. In this case, isValidating
doesn't help much.
In this release, useSWR
, useSWRInfinite
and useSWRImmutable
will return an extra isLoading
state along with the isValidating
state. They will fit to different scenarios:
function Stock() {
const { data, isLoading, isValidating } = useSWR(STOCK_API, fetcher, {
refreshInterval: 3000
});
// If it's still loading the initial data, there is nothing to display.
// We return a skeleton here.
if (isLoading) return <div className="skeleton" />;
// Otherwise, display the data and a spinner that indicates a background
// revalidation.
return (
<>
<div>AAPL ${data}</div>
{isValidating ? <div className="spinner" /> : null}
</>
);
}
In the example above, we display a skeleton while loading the data. After the data is loaded, we show a spinner next to the data whenever we are re-fetching (revalidating):
You can find the full code for this example here: https://codesandbox.io/s/swr-isloading-v8dfpy.
New keepPreviousData
option (#1929)
When doing data fetching based on continuous user actions, e.g. real-time search when typing, keeping the previous fetched data can improve the UX a lot.
In SWR 2.0, there is now a keepPreviousData
option to enable that behavior. Here's a simple search UI:
function Search() {
const [search, setSearch] = React.useState('');
const { data, isLoading } = useSWR(`/search?q=${search}`, fetcher, {
keepPreviousData: true
});
return (
<div>
<input
type="text"
value={search}
onChange={(e) => setSearch(e.target.value)}
placeholder="Search..."
/>
<div className={isLoading ? "loading" : ""}>
{data?.products.map(item => <Product key={item.id} name={item.name} />)
</div>
</div>
);
}
With keepPreviousData
enabled, you will still get the previous data
even if you change the SWR key and the data for the new key starts loading again. This improves the visual continuity quite a lot, the search feels smoother after flipping the switch:
CleanShot.2022-04-17.at.02.57.44.mp4
You can find the full code for this example here: https://codesandbox.io/s/swr-keeppreviousdata-fsjz3m.
Breakings
Type InfiniteFetcher
is renamed to SWRInfiniteFetcher
(#1930)
This type was already marked as deprecated in 1.x, and it now removed in this beta. If you are using it, please do the following change:
- import { InfiniteFetcher } from 'swr/infinite'
+ import { SWRInfiniteFetcher } from 'swr/infinite'
What's Changed
- breaking: drop legacy types by @huozhi in #1930
- feat:
isLoading
state and refactor the core by @shuding in #1928 - feat:
keepPreviousData
option by @shuding in #1929 - chore: refactoring by @shuding in #1925
- fix: output mutation cjs bundle with named exports by @huozhi in #1926
Full Changelog: 2.0.0-beta.0...2.0.0-beta.1
2.0.0-beta.0
SWR 2.0 coming soon, and this is the first beta version!
Keep in mind that APIs might still change until the stable release. Documentation will also be updated once stable.
💖 Give feedback in discussion: #1919.
Highlights
useSWRMutation — dedicated API for remote mutations, e.g. POST (#1450)
Added in #1450, the new useSWRMutation
hook covers all the use cases of:
- Requests that change data on the remote side: such as POST, PUT, DELETE, etc.
- Requests that need to be triggered manually, instead of automatically by SWR.
- Passing extra argument to fetcher, when triggering a request.
- Knowing the status of a mutation, similar to
isValidating
but for mutations. - A lot more...
Here's a quick example of how it looks:
import useSWRMutation from 'swr/mutation'
async function sendRequest(url, { arg }) {
return fetch(url, {
method: 'POST',
body: JSON.stringify(arg)
})
}
function App() {
const { trigger } = useSWRMutation('/api/user', sendRequest)
return <button onClick={() => {
trigger({ username: 'johndoe' })
}}>Create User</button>
}
In this example, the "fetcher", which is sendRequest
, will receive the value { username: 'johndoe' }
as the arg
from the second parameter. The request will only be triggered when clicking the button.
The new useSWRMutation
hook is actually more powerful than this, it also supports:
- Optimistic updates
- Automatic error rollback
- Detect and avoid race conditions between
useSWR
- Populate the cache of
useSWR
after mutation finishes - ...
More examples to come.
Breakings
Fetcher no longer accepts multiple arguments (#1864)
Previously, if the key
is an array, the values will be passed to the fetcher
function as arguments separately. In 2.0, the key
will always be passed to the fetcher
as is.
Before:
// SWR 1.x
useSWR([1, 2, 3], (a, b, c) => {
assert(a === 1)
assert(b === 2)
assert(c === 3)
})
After 2.0.0:
// SWR 2.0.0
useSWR([1, 2, 3], (a) => {
assert(a === [1, 2, 3])
})
Internal structure of the cached data (#1863)
This change affects the code that directly reads/writes to the cache, or provides a cache preset. For example if you have something like cache.set(key, value)
, you'll have to update your code.
Previously, the cached value of key
will be the associated data
, so this was guaranteed:
// SWR 1.x
assert(cache.get(key) === data)
And we keep other states (error
, isValidating
) with a special, prefixed key. Something like '$err$' + key
.
Since 2.0.0, the internal structure will be an object that holds all the current states:
// SWR 2.0.0
assert(cache.get(key) === { data, error, isValidating })
So you will have to do the following change to your code, get
:
- cache.get(key)
+ cache.get(key)?.data
And set
:
- cache.set(key, data)
+ cache.set(key, { ...cache.get(key), data })
What's Changed
- bugfix: make suspense and revalidateIfStale work together by @simowe in #1851
- feat: support functional optimisticData by @huozhi in #1861
- test: fix an act warning by @koba04 in #1888
- breaking: Change the fetcher argument to be consistent with the passed key by @shuding in #1864
- Keep all fields with one single key by @shuding in #1863
- fix: wrong spell by @baojie223 in #1902
- Update React 18 dependencies by @shuding in #1824
- build(deps): bump minimist from 1.2.5 to 1.2.6 by @dependabot in #1903
- type: make type inference simpler by @promer94 in #1908
- type: fix type error on SWRConfig by @himself65 in #1913
- feat: Hook for remote mutations by @shuding in #1450
New Contributors
- @simowe made their first contribution in #1851
- @baojie223 made their first contribution in #1902
- @himself65 made their first contribution in #1913
Full Changelog: 1.2.2...2.0.0-beta.0
1.3.0
What's Changed
- type: fix type error on SWRConfig by @himself65 in #1913
- chore: update React 18 dependencies by @shuding in #1824
- test: fix an act warning by @koba04 in #1888
- feat: support functional optimisticData by @huozhi in #1861
- bugfix: make suspense and revalidateIfStale work together by @simowe in #1851
Full Changelog: 1.2.2...1.3.0
1.2.2
Highlights of This Release
populateCache
Option Now Supports Function
We added better Optimistic UI support in v1.2.0. However, what if your API is only returning a subset of the data (such as the mutated part), that can be populated into the cache? Usually, an extra revalidation after that mutation is needed. But now you can also use a function as populateCache
to transform the mutate result into the full data:
await mutate(addTodo(newTodo), {
optimisticData: [...data, newTodo],
rollbackOnError: true,
populateCache: (addedTodo, currentData) => {
// `addedTodo` is what the API returns. It's not
// returning a list of all current todos but only
// the new added one.
// In this case, we can transform the mutate result
// together with current data, into the new data
// that can be updated.
return [...currentData, addedTodo];
},
// Since the API already gives us the updated information,
// we don't need to revalidate here.
revalidate: false,
});
The new definition:
populateCache?: boolean | ((mutationResult: any, currentData: Data) => Data)
Here is a demo for it: https://codesandbox.io/s/swr-basic-forked-hi9svh
Bug Fixes
What's Changed
- refactor: revalidateIfStale has an effect on updates, not only mounting by @koba04 in #1837
- fix: reset stale unmountedRef in suspense by @promer94 in #1843
- test: add a test for the behavior of revalidateOnMount when the key has been changed by @koba04 in #1847
- feat: Support
populateCache
as a function by @shuding in #1818
Full Changelog: 1.2.1...1.2.2