-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Render based on previous state while async selector is pending. #290
Comments
Async selectors need to be "pure", that is always return the same results for a given set of input dependency values. However, if you are fetching data based on state from dependent atoms/selectors, or using a pattern such as Internally, we have situations we want to show a spinner overlay on a chart with old data, while the new data is loading. We played with some React component abstraction that will take asynchronous dependencies, use However, a proper solution is in the works. When we finish support for React Concurrent Mode, then there is a transition hook you can use to render from the old state to the new. cc @davidmccabe to update when that's available. |
@drarmstr so as for now I need to keep additional atom to keep state from async selector and check on selector state to decide which value to use, right? |
>> Async selectors need to be "pure", that is always return the same
results for a given set of input dependency values.
I think the proper word in this context is "idempotent".
An idempotent function can cause idempotent side-effects. A pure function
cannot.
https://stackoverflow.com/questions/1077412/what-is-an-idempotent-operation
https://stackoverflow.com/questions/4801282/are-idempotent-functions-the-same-as-pure-functions
…On Wed, Jun 10, 2020 at 3:45 AM Sergiy Babich ***@***.***> wrote:
@drarmstr <https://github.com/drarmstr> so as for now I need to keep
additional atom to keep state from async selector and check on selector
state to decide which value to use, right?
—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
<#290 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AADBBIHWBJBYG5MMPJN4LQLRV5B2RANCNFSM4NZQWJQQ>
.
|
Probably, just extend |
@babichss - In our wrapper we used React state to store the previous |
Implemented temporary solution for this problem, in case someonr has same issues. import { useEffect, useState } from 'react';
import { RecoilValue, useRecoilValueLoadable } from 'recoil';
export function useLoadable<T>(
defaultValue: Partial<T>,
recoilLoadable: RecoilValue<T>,
pick?: [keyof T],
): [T, 'loading' | 'hasValue' | 'hasError'];
export function useLoadable<T>(
defaultValue: Partial<T>,
recoilLoadable: RecoilValue<T>,
pick?: [keyof T],
): [Partial<T>, 'loading' | 'hasValue' | 'hasError'] {
const [value, setValue] = useState(defaultValue);
const recoilValue = useRecoilValueLoadable<T>(recoilLoadable);
let returnValue: Partial<T> = defaultValue;
useEffect(() => {
if (recoilValue.state === 'hasValue' && recoilValue.contents !== value) {
setValue(recoilValue.contents);
}
}, [recoilValue.contents, recoilValue.state, value]);
if (recoilValue.state !== 'hasValue' && value) {
if (pick) {
returnValue = pick.reduce((res, key) => ({ ...res, [key]: value[key] }), {});
} else {
returnValue = value;
}
}
if (recoilValue.state === 'hasValue') {
returnValue = recoilValue.contents;
}
return [returnValue, recoilValue.state];
} |
Yet another solution that memoizes the last value; export function useData() {
const value = useRecoilValueLoadable(Data);
const ref = React.useRef();
if (value.state === "hasValue") {
ref.current = value
}
return value.state === "loading" && ref.current?.state === "hasValue" ? ref.current : value;
} |
My own spin on @koistya's answer to show that data is loading while displaying the previous data. export function useRecoilValueBackgroundLoadable<T>(
data: RecoilValue<T>
): [Loadable<T>, { isLoading: boolean }] {
const value = useRecoilValueLoadable(data);
const ref = React.useRef<typeof value>();
if (value.state === "hasValue") {
ref.current = value;
}
return [
value.state === "loading" && ref.current?.state === "hasValue"
? ref.current
: value,
{ isLoading: value.state === "loading" },
];
}
const MyComponent = () => {
const refresh = useRecoilRefresher_UNSTABLE(thingsQuery);
const [things, { isLoading }] = useRecoilValueBackgroundLoadable(thingsQuery);
let content;
if (things.state === "hasError") {
content = <div>Error loading things</div>;
} else if (things.state === "hasValue") {
content = <ThingsTable projects={things.getValue()} />;
} else {
content = <LoadingMessage />;
}
return (
<>
<button onClick={() => refresh()} disabled={isLoading}>
Refresh
</button>
{content}
</>
);
}; |
Yet another small adaptation. If there's something wrong with this, please let me know - but it seems to work perfectly to suspend only on the first load and then keep showing the "latest and greatest" without re-suspending
Usage:
|
https://github.com/cronocodesolutions/recoil-utils
|
My case is very simple.
I want to be able to read old data while new data is fetching with async selector which is depending on some changing value.
Is it possible?
The text was updated successfully, but these errors were encountered: