|
| 1 | +import { Suspense, useEffect } from 'react' |
1 | 2 | import { fireEvent, render } from '@testing-library/react' |
2 | 3 | import { Atom, atom } from 'jotai' |
3 | 4 | import { loadable, useAtomValue, useUpdateAtom } from 'jotai/utils' |
@@ -130,13 +131,76 @@ it('loadable can recover from error', async () => { |
130 | 131 | await findByText('Data: 6') |
131 | 132 | }) |
132 | 133 |
|
| 134 | +it('loadable immediately resolves sync values', async () => { |
| 135 | + const syncAtom = atom(5) |
| 136 | + const effectCallback = jest.fn() |
| 137 | + |
| 138 | + const { getByText } = render( |
| 139 | + <Provider> |
| 140 | + <LoadableComponent effectCallback={effectCallback} asyncAtom={syncAtom} /> |
| 141 | + </Provider> |
| 142 | + ) |
| 143 | + |
| 144 | + getByText('Data: 5') |
| 145 | + expect(effectCallback.mock.calls).not.toContain( |
| 146 | + expect.objectContaining({ state: 'loading' }) |
| 147 | + ) |
| 148 | + expect(effectCallback).toHaveBeenLastCalledWith({ state: 'hasData', data: 5 }) |
| 149 | +}) |
| 150 | + |
| 151 | +it('loadable can use resolved promises syncronously', async () => { |
| 152 | + const asyncAtom = atom(Promise.resolve(5)) |
| 153 | + const effectCallback = jest.fn() |
| 154 | + |
| 155 | + const ResolveAtomComponent = () => { |
| 156 | + useAtomValue(asyncAtom) |
| 157 | + |
| 158 | + return <div>Ready</div> |
| 159 | + } |
| 160 | + |
| 161 | + const { getByText, findByText, rerender } = render( |
| 162 | + <Provider> |
| 163 | + <Suspense fallback={null}> |
| 164 | + <ResolveAtomComponent /> |
| 165 | + </Suspense> |
| 166 | + </Provider> |
| 167 | + ) |
| 168 | + |
| 169 | + await findByText('Ready') |
| 170 | + |
| 171 | + rerender( |
| 172 | + <Provider> |
| 173 | + <LoadableComponent |
| 174 | + effectCallback={effectCallback} |
| 175 | + asyncAtom={asyncAtom} |
| 176 | + /> |
| 177 | + </Provider> |
| 178 | + ) |
| 179 | + getByText('Data: 5') |
| 180 | + |
| 181 | + expect(effectCallback.mock.calls).not.toContain( |
| 182 | + expect.objectContaining({ state: 'loading' }) |
| 183 | + ) |
| 184 | + expect(effectCallback).toHaveBeenLastCalledWith({ state: 'hasData', data: 5 }) |
| 185 | +}) |
| 186 | + |
133 | 187 | interface LoadableComponentProps { |
134 | | - asyncAtom: Atom<Promise<number> | Promise<string>> |
| 188 | + asyncAtom: Atom<Promise<number> | Promise<string> | string | number> |
| 189 | + effectCallback?: (loadableValue: any) => void |
135 | 190 | } |
136 | 191 |
|
137 | | -const LoadableComponent = ({ asyncAtom }: LoadableComponentProps) => { |
| 192 | +const LoadableComponent = ({ |
| 193 | + asyncAtom, |
| 194 | + effectCallback, |
| 195 | +}: LoadableComponentProps) => { |
138 | 196 | const value = useAtomValue(loadable(asyncAtom)) |
139 | 197 |
|
| 198 | + useEffect(() => { |
| 199 | + if (effectCallback) { |
| 200 | + effectCallback(value) |
| 201 | + } |
| 202 | + }, [value, effectCallback]) |
| 203 | + |
140 | 204 | if (value.state === 'loading') { |
141 | 205 | return <>Loading...</> |
142 | 206 | } |
|
0 commit comments