Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(utils/useHydrateAtoms) - Optionally rehydrate with force hydrate #1990

Merged
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions docs/utilities/ssr.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,20 @@ useHydrateAtoms(new Map([[count, 42]]))
```

Atoms can only be hydrated once per store. Therefore, if the initial value used is changed during rerenders, it won't update the atom value.
If there is a unique need to re-hydrate a previously hydrated atom, pass the optional dangerouslyForceHydrate as true
and note that it may behave wrongly in concurrent rendering.

```js
useHydrateAtoms(
[
[countAtom, 42],
[frameworkAtom, 'Next.js'],
],
{
dangerouslyForceHydrate: true,
}
)
```

If there's a need to hydrate in multiple stores, use multiple `useHydrateAtoms` hooks to achieve that.

Expand Down
6 changes: 4 additions & 2 deletions src/react/utils/useHydrateAtoms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ import { useStore } from '../../react.ts'
import type { WritableAtom } from '../../vanilla.ts'

type Store = ReturnType<typeof useStore>
type Options = Parameters<typeof useStore>[0]
type Options = Parameters<typeof useStore>[0] & {
dangerouslyForceHydrate?: boolean
}
type AnyWritableAtom = WritableAtom<unknown, any[], any>
type AtomMap<A = AnyWritableAtom, V = unknown> = Map<A, V>
type AtomTuple<A = AnyWritableAtom, V = unknown> = readonly [A, V]
Expand Down Expand Up @@ -36,7 +38,7 @@ export function useHydrateAtoms<T extends Iterable<AtomTuple>>(

const hydratedSet = getHydratedSet(store)
for (const [atom, value] of values) {
if (!hydratedSet.has(atom)) {
if (!hydratedSet.has(atom) || options?.dangerouslyForceHydrate) {
hydratedSet.add(atom)
store.set(atom, value)
}
Expand Down
75 changes: 75 additions & 0 deletions tests/react/utils/useHydrateAtoms.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -240,3 +240,78 @@ it('useHydrateAtoms should respect onMount', async () => {
await findByText('count: 42')
expect(onMountFn).toHaveBeenCalledTimes(1)
})

it.only('passing dangerouslyForceHydrate to useHydrateAtoms will re-hydrated atoms', async () => {
dai-shi marked this conversation as resolved.
Show resolved Hide resolved
const countAtom = atom(0)
const statusAtom = atom('fulfilled')

const Counter = ({
initialCount,
initialStatus,
dangerouslyForceHydrate = false,
}: {
initialCount: number
initialStatus: string
dangerouslyForceHydrate?: boolean
}) => {
useHydrateAtoms(
[
[countAtom, initialCount],
[statusAtom, initialStatus],
],
{
dangerouslyForceHydrate,
}
)
const [countValue, setCount] = useAtom(countAtom)
const [statusValue, setStatus] = useAtom(statusAtom)

return (
<>
<div>count: {countValue}</div>
<button onClick={() => setCount((count) => count + 1)}>dispatch</button>
<div>status: {statusValue}</div>
<button
onClick={() =>
setStatus((status) =>
status === 'fulfilled' ? 'rejected' : 'fulfilled'
)
}>
update
</button>
</>
)
}
const { findByText, getByText, rerender } = render(
<StrictMode>
<Counter initialCount={42} initialStatus="rejected" />
</StrictMode>
)

await findByText('count: 42')
await findByText('status: rejected')
fireEvent.click(getByText('dispatch'))
fireEvent.click(getByText('update'))
await findByText('count: 43')
await findByText('status: fulfilled')

rerender(
<StrictMode>
<Counter initialCount={65} initialStatus="rejected" />
</StrictMode>
)
await findByText('count: 43')
await findByText('status: fulfilled')

rerender(
<StrictMode>
<Counter
initialCount={11}
initialStatus="rejected"
dangerouslyForceHydrate={true}
/>
</StrictMode>
)
await findByText('count: 11')
await findByText('status: rejected')
})