-
Notifications
You must be signed in to change notification settings - Fork 1
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
Suspense and Concurrent Mode in ReSift #32
Comments
I have some new news about supporting suspense and concurrent mode: https://stackoverflow.com/questions/58611408/what-kinds-of-state-updates-can-we-defer-with-usetransition TL;DR, we have to remove redux and replace it with React context. I don't think this will be too difficult but it's something to do. |
I have a CodeSandbox with working but naive implementation of Suspense. This demo uses https://codesandbox.io/s/resift-rentals-with-suspense-nnzfz It works by throwing a promise that subscribes to the redux store. It re-runs the selector until the data is present. After that, it resolves the promise and tells suspense that we can render. This works but it's problematic bc (from the post above), redux state is incompatible with I'll report back with more experiments! |
You might want to try react-tracked. |
Alright I'm back with some experiments and I think I have more direction on how concurrent mode should work in ReSift. First I want to establish some goals and better define what "supporting suspense and concurrent mode" means for ReSift 👇 The goal should be to support suspense and concurrent mode as much as Relay does. This means to utilize the React APIs Relay utilizes. The objective is then to have the same UX benefits as Relay. I've been reading their experimental docs, and it's some pretty dense stuff 🤯 To me, supporting suspense and concurrent mode for ReSift means establishing a framework of using:
Out of the suspense supporting data fetching libs, I have yet to see one support useTransition or even mention SuspenseList so really the only reference implementation that does all of this is Relay. Here are the two libs I've been keeping an eye on (let me know if there are more): I also checked to see if the apollo client is working on anything and I didn't see nothin. Supporting
|
Hey @ricokahler, your comment is packed with good thoughts! I feel that I need to read more into each linked article to offer insights. For now, I have a very general question: By 'supporting suspense and concurrent mode', do you just mean that ReSift will function as expected when users have concurrent mode enabled, or do you mean re-writing part of ReSift to use suspense, or, do you mean adding new ReSift apis that have concurrent mode built in so ReSift users can get the benefits without having to write code with suspense and other concurrent mode hooks themselves? |
@pearlzhuzeng I mean a combination of all three in some ways 😅. It's interesting. The high-level goal I outlined above is more or less to have ReSift have the same UX benefits as Relay. This means defining some framework that uses Suspense and Concurrent Mode to get those UX benefits. Lower level this means (afaik):
So to answer your questions more directly:
Yes but there's more to it. Because our goal is to have the same UX benefits as Relay, we have to create some framework to not only works with concurrent mode enabled but has extra UX benefits when using this framework with concurrent mode.
I think supporting suspense and concurrent mode does involve re-writing parts of ReSift but I don't think ReSift's internal APIs will use
From my answer above, I think the right approach is to make ReSift work with the official React APIs. Right now, I don't think this means adding any new APIs or using the React API internally in ReSift. I think our current APIs will actually work very nicely with suspense and concurrent mode. For example, I'm envisioning this is how suspense + concurrent mode + resift code could look like: import React, { Suspense, useTransition, lazy } from 'react';
import { useDispatch, useData } from 'resift';
import makeGetPost from './makeGetPost';
const PostContent = lazy(() => import('./PostContent'));
const useStyles = /* ... */;
function Post({ id }) {
const classes = useStyles();
const dispatch = useDispatch();
const [startTransition, isPending] = useTransition({ timeoutMs: 3000 });
const getPost = makeGetPost(id);
useEffect(() => {
startTransition(() => {
dispatch(getPost());
});
}, [getPost]);
return (
<div>
<h1>Post:</h1>
<Suspense fallback={<SkeltonPost />}>
<PostContent
fetch={getPost}
className={classNames({
[classes.transitioning]: isPending,
})}
/>
</Suspense>
</div>;
);
}
export default Post; import React from 'react';
import { useData } from 'resift';
function PostContent({ fetch }) {
const post = useData(fetch);
return (
<div>
<h2>{post.title}</h2>
{/* ... */}
</div>
);
}
export default PostContent; |
I tweeted Dan Abramov and Andrew Clark with some suspense questions and my initial thought of "react freezes the state you're transitioning" is completely wrong lol. I don't think we need to re-architect our state shape to consider a react "freezing" state because that's incorrect. Instead, now I'm thinking we need to get ReSift state into a react-manage state and then solve the "what to throw" problem. |
Hi, that's what we call "state branching". It's not official words. |
@dai-shi oh that’s fascinating. thanks for sharing! |
The React team has finally released docs for suspense for data fetching and concurrent mode 🎉. They've clarified a bit of how it works and how its intended to be used, and my first impression is that it can actually fit within our current APIs nicely without any big breaking changes.
Reading their docs is a prereq to this issue. and so is watching this talk from the Relay team.
They say the correct way to use suspense is to "render as you fetch", meaning that you reveal parts of the API that make sense to reveal if you have enough data to reveal it. Here is a direct quote from their new docs:
which sums up the idea of "render as you fetch".
What does this mean for ReSift?
Well, fortunately, not that much. Many simple fetching libraries rely on "fetch-on-render" techniques because the state of the fetches inflight live in the component. Fortunately for ReSift, the state of our fetches lives in global state, and we've already discovered that we need to split up fetching (e.g.
dispatch(getPerson())
) from getting the data (e.g.useData(getPerson)
).This means that we can advise users to initiate fetches for components at some higher component level to abide by the "already being fetched"/"fetch-as-you-render" philosophy. This is possible because our
useDispatch
anduseData
APIs are separate.Initial thoughts on Suspense and Concurrent Mode in ReSift (revised)
Here's how I think it should work (disclaimer: i have not tested to see if this will work):
useData
should suspend by default (i.e. throw a promise) and we should add a configuration object with the flagnoSuspend
(or maybeallowNull
or similar) to allow the hook to returnnull
instead of causing suspense (e.g.useData(getPerson, { noSuspend: true })
).we makedispatch
work withstartTransition
ofuseTransition
I still have to test whether this approach makes sense but there's my initial thoughts on how that should work.
Adoption strategy/road map concerns
Suspense + concurrent mode should stay in an experimental as long as React has them in an experimental channel. See #37 for more details.
The text was updated successfully, but these errors were encountered: