useAsync: exported `loading` is false when the promise factory is not yet called
What is the current behavior?
When the dependencies given to the useAsync change, the promise factory is called and the exported loading will return true while the promise is not yet resolved. However, this is internally implemented using useEffect, which is called after the hook is returned. This results in a temporary inconsistent state between the input, the output result and the loading variable.
Steps to reproduce it and if possible a minimal demo of the problem. Your bug will get fixed much faster if we can run your code and it doesn't have extra dependencies other than react-use. Paste the link to your JSFiddle or CodeSandbox example below:
The following testcase should illustrate the issue. When rerender is called, the expect within the component fails because it is not considered to be loading, while input and output do not match.
it('should always be considered loading when value is out of sync', async () => {
function Component({inputValue}: {inputValue: string}): React.ReactElement|null {
const { value, loading } = useAsync(() => {
return Promise.resolve(inputValue);
}, [ inputValue ]);
if (loading) {
return null;
}
expect(inputValue).toEqual(value);
return <span>{value}</span>;
}
const { rerender, container } = render(<Component inputValue="a"/>);
expect(container.innerHTML).toEqual('');
await waitFor(() => expect(container.innerHTML).toEqual('<span>a</span>'));
act(() => {
rerender(<Component inputValue="b"/>);
});
expect(container.innerHTML).toEqual('');
await waitFor(() => expect(container.innerHTML).toEqual('<span>b</span>'));
});
What is the expected behavior?
What I would like to be able to do is check the loading variable, and if false be certain that the output is created from the input.
A workaround exists by returning both the input and the output from the promise factory as a tuple or object, but I expected I would be able to rely on the loading export.
A little about versions:
-
react-use: master
I will follow this up with a PR later today.
Having this long-standing issue is awkward. The ideologically async way is to include such inputs as part of the outputs. This is messy to do manually because outputs may be either "value" or "error".
Maybe include inputs in state automatically?
Current:
isMounted() && callId === lastCallId.current && set({ error, loading: false });
With inputs:
isMounted() && callId === lastCallId.current && set({ error, loading: false, deps });
And the expected use:
const {loading, value, error, deps: [idLoaded]} = useAsync(..., [id]);
someFunc(idLoaded, value); // this id was used to get the value; and is undefined while none loaded yet