Closed pakettiale closed 2 years ago
Thanks for using jotai and investigating atomWithHash. To be honest, I haven't thought about using it with router. So, it's an open field.
a) atomWithHash might be able to improved to handle your use case, or
b) it may not suit for you because hashchange
is the key for atomWithHash (in such a case another atomWithFoo based on atomWithStorage might be possible.)
I'm not sure which is good for now. I don't think I fully understand the problem yet. Would it be possible for you to create a minimal example with codesandbox? What's the router library you use?
Fix our problem some time ago and commenting here in case someone else stumbles upon this issue.
This was actually due to using a stale setter function and had nothing to do with routing. Our state contained an object with many fields and to make state updates cleaner we had a wrapper for setter like this
const [urlParams, _setUrlParams] = useAtom(FilterState(name));
const setUrlParams = (input: Partial<UrlParams>) =>
_setUrlParams({ ...urlParams, ...input });
The setUrlParams
was then passed to multiple child components. Stale state setters then created issues when multiple setUrlParams calls were made in a short amount of time.
To fix this we could have changed setUrlParams
to this
const setUrlParams = _setUrlParams((input: Partial<UrlParams>) =>
{ ...urlParams, ...input });
But for reusability we created a record atom that handles partial state updates by filling the missing top level fields from the existing state.
import { Atom, useAtomValue, useSetAtom, WritableAtom } from 'jotai';
import { Scope, SetAtom } from 'jotai/core/atom';
import { useCallback } from 'react';
type Awaited<T> = T extends Promise<infer V> ? Awaited<V> : T;
/**
* QoL hook for record atoms. Setter updates the state by default instead of
* replacing it. Setter uses callback form to prevent stale setter
* functions in child components.
*/
export function useRecordAtom<
Value,
Update,
Result extends void | Promise<void>
>(
atom: Atom<Value> | WritableAtom<Value, Update, Result>,
scope?: Scope
): [Awaited<Value>, SetAtom<Update, Result>] {
/**
* jotai has problems with their type casting which forces us to use ts-ignore
* see here: https://github.com/pmndrs/jotai/blob/042a9027bf59662962d2db73b9639734f4afdf3f/src/core/useAtom.ts#L29
*/
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const setter = useSetAtom(atom as SetAtom<Update, Result>, scope);
const memoizedRecordSetter = useCallback(
(newValue: Value) => {
setter((oldValue: Value) => ({ ...oldValue, ...newValue } as Value));
},
[setter]
);
return [
useAtomValue(atom, scope),
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
memoizedRecordSetter,
];
}
We have a strange problem with our react application where multiple
atomWithHash
mounting at the same time cause the subscribe function ofatomWithHash
to behave badly. But only sometimes. I'm also thinking whether the atomWithHash is suitable for our use.The application is composed like this:
The problem:
page1atom
flashes briefly after which it changes into theinitialState1
, the state of thefilterAtom("page1")
is remembered correctly. Hash now containsfilterAtom
state but nothing frompage1atom
.Curiously enough this same problem does not happen in the
Page2
component even though they are quite similar components (both display a list of things). I guess there is some kind of data race happening when both atoms try to read/write the urlHash at the same time.If I modify the
atomWithHash
like this so that thesubscribe
field ofhashStorage
does not initialize the value in case the corresponding part of hash is missing, then the problem disappears. But now the hash is updated only after I make some state change.What we are trying to achieve with jotai and
atomWithHash
is:Is
atomWithHash
an overkill for the point 2? We only care about reading the url when the application is first loaded / refreshed. Otherwise the URL needs only be write only. Thus listening tohashchange
events seems unnecessary. And if I understood the code correctly this can cause unnecessary rerenders when otheratomWithHash
es are updating the hash.P.S. jotai is a very nice library, thanks for creating it :)
Edited the original post:
urlParamStorage
intohashStorage
, this was an artefact left from my own testing