swr
swr copied to clipboard
TypeError undefined is not a function error on mutate
After upgrading to swr 1.1 (currently using 1.1.1) we have observed the following behavior:
When calling mutate
on a key that is no cached, a TypeError undefined is not a function
error will be observed.
For Example:
const { mutate } = useSWRConfig()
mutate(
ITEMS_KEY,
(items: Item[]) =>
items?.map((p) => (p.id === itemId ? update(p) : p)),
false,
)
if ITEMS_KEY
was already loaded and cached, there will be no error. Also if I try to try/catch or do null checks inside the updater function, the error still happens. Worth noting we are using a custom cache implementation and this is with React Native.
I believe it somehow has to do with how that function is transformed into a generator function and the transpiled code might be doing some additional checks, because I can't console log it or debug it or try/catch it.
Any clues as to why this might be happening, or any suggestions as to how we might be using mutate
incorrectly would be greatly appreciated.
Hi @beetlebum ,
I tried to write a failing test for this case:
it.only('gracefully handles mutate on non existing keys', async () => {
const fetcher = jest.fn(() => 'data')
const mutSpy = jest.fn()
const key = createKey()
const mutKey = createKey()
function Page() {
const { mutate } = useSWRConfig()
useSWR(key, fetcher)
return (
<div>
<span>Hello</span>
<button
onClick={() =>
mutate(
mutKey,
() => {
mutSpy()
return undefined
},
false
)
}
>
mutate
</button>
</div>
)
}
renderWithConfig(<Page />)
await screen.findByText('Hello')
fireEvent.click(screen.getByRole('button'))
expect(mutSpy).toHaveBeenCalledTimes(1)
screen.getByText('Hello')
})
But I observe no crashes. At least I am able to still see rendered content on screen.
In your snippet, where does update
come from? Could you perhaps point how I could edit the test case above to reproduce your issue?
Thanks for this @icyJoseph . I am quite confused by the whole thing so maybe let me give you more context:
- This is in a React Native app, using the Hermes js engine.
- SWRConfig includes a custom provider for the cache.
const mmkvProvider: () => ApiCache<any> = () => ({
get: (key: string) => {
const res = storage.getString(toKey(key));
if (res) return JSON.parse(res);
return res;
},
set: (key: string, value: any) => {
if (value) {
storage.set(toKey(key), JSON.stringify(value));
} else {
storage.set(toKey(key), '');
}
},
keys: () => storage.getAllKeys(),
clear: () => storage.clearAll(),
delete: (key: string) => {
storage.delete(toKey(key));
},
has: (key) => !!storage.getString(toKey(key)),
});
This provider has been working for a while now.
- The crash is being reported thousands of times since releasing a version with SWR 1.1.0
- Even though there's an exception, it does not crash the app (probably not running on the main thread?)
The code does something like this:
const { mutate } = useSWRConfig()
const editItem = async (itemId, update) => {
try {
await Promise.all([
mutate(`item_key_${itemId}`, item => item? update(item) : item),
mutate(key_2, item => item.id === itemId? update(item) : item),
mutate(key_3, items => items.map(i => i.id === itemId? update(i) : i)), // key_3 is not cached yet
])
} catch (error) {
// This catch does not fire
}
}
Is it possible to create a minimal reproduction? The crash shouldn't be related to React Native I think, but the custom cache provider seems suspicious.
I have some more information about this issue, we're now using 1.3.0 but the issue still persists (React Native App).
We see many errors (does not crash the app) when trying to mutate a key that does not have a cached value, for example when trying to preload some content or update a local cache when the items was not loaded yet.
There's a stack trace now:
Error: Cannot convert undefined value to object
node_modules/swr/dist/index.js in __generator$argument_1 at line 298:66
_a = serialize(_key), key = _a[0], keyInfo = _a[2];
if (!key)
return [2 /*return*/];
_b = SWRGlobalState.get(cache), MUTATION = _b[2];
The last row is 298.
Please let me know if you need any more information.
In case it helps: I ran into this issue myself when calling global mutate with a function in React Native, and removing the custom cache provider from my SWR config makes the issue go away. I narrowed it down to my get()
function not handling non-string
key types.