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

Support useTransition() for showing previous Recoil state with Recoil state changes. #759

Closed
trebor opened this issue Nov 22, 2020 · 29 comments
Assignees
Labels
enhancement New feature or request

Comments

@trebor
Copy link

trebor commented Nov 22, 2020

i realize that useTransition is bleeding edge, but i'd like to get an early handle on it. this far i've not been able to figure out any combination of parts to get useTransition to work, and i can't find any examples online. all my imports work and i'm following the code like this.

    recoil: 0.1.1
    react: 0.0.0-experimental-4ead6b530
    react-dom: 0.0.0-experimental-4ead6b530

and using the concurrent pattern:

ReactDOM.unstable_createRoot(document.getElementById("root")).render(
  <BrowserRouter>
    <Route path="/" component={App} />
  </BrowserRouter>
);

and an otherwise typical and working <Suspense /> pattern:

import React, { Suspense, useState, unstable_useTransition as useTransition } from "react";
import { atom, selector, useRecoilState } from "recoil";

export const baseState = atom({
  key: "base",
  default: 100,
});

export const resourceState = selector({
  key: "resource",
  get: ({ get }) => {
    const base = get(baseState);
    return new Promise(resolve => {
      setTimeout(() => {
        resolve(base);
      }, 2000);
    });
  },
  set: ({ set }, value) => {
    set(baseState, value);
  },
});

const UseTransitionEample = () => {
  const [resource, setResource] = useRecoilState(resourceState);
  const [startTransition, isPending] = useTransition({ timeoutMs: 3000 });

  return (
    <>
      <div>resource: {resource}</div>
      <div>isPending: {isPending ? "true" :  "false"}</div>
      <button onClick={() =>
          startTransition(() => {
            setResource(resource + 1);
          })
      }>Update</button>
    </>
  );
}

const SuspenseWrapper = () => (
  <Suspense fallback={<div>"Loading..."</div>}>
    <UseTransitionEample />
  </Suspense>
);

export default SuspenseWrapper;

In the above code clicking the button results in the "Loading..." fallback being presented for the full duration of the "load".

@trebor trebor changed the title example of recoil and useTransition request for example of recoil and useTransition Nov 24, 2020
@drarmstr drarmstr added the help wanted Extra attention is needed label Nov 25, 2020
@ghost
Copy link

ghost commented Nov 27, 2020

@trebor the current useTransition hook is expecting a returned iterable object or a function. Which is the error I am receiving when replicating your issue.

I would resort to this example brought about by React's documentation:

https://codesandbox.io/s/jovial-lalande-26yep?file=/src

I did actually have it load and update the number when clicked after adjusting the returned value.

@trebor
Copy link
Author

trebor commented Nov 29, 2020

@algorithmhash thanks for the response. when you say "useTransition hook is expecting a returned iterable object or a function" i assume you are saying i should be doing that in my code? can you point me to the line number where that is happing in the example? in that example on line 24 i see the useTransition passed a configuration object. is there some other place where i should be passing the iterable or function?

@ghost
Copy link

ghost commented Dec 2, 2020

Hey, @trebor sorry about the late reply! Normally when we are loading something it pertains to some amount of data. Usually, a large data set or even a small one. Within any data set we have an array of objects and may be nested objects and so on. So here is an updated resource, utilizing the environment that you have provided, you can see where I created an array of a single object with an object key called number with a value that, if not put in this format, would in turn error out🙂 . I hope this works and please do respond if it helps or does not :] .

Use Transition Hook example

@trebor
Copy link
Author

trebor commented Dec 2, 2020

oh, @algorithmhash this is amazing! i regret not putting together the code sandbox in the first place, thank you! i will do so for any future questions.

my understanding of useTransition is that it will keep the old DOM in place while data is loading, up to the timeoutMs specified. in my browser, when i click the update button, i instantly see the "Loading..." fallback replace the existing DOM:

image

which is not what i expected. i had expected the above to remain visible because the "load time" of 2000ms is less than the 3000ms specified to useTransition.

is my understanding of useTransition wrong?

am i using in the wrong way?

does recoil maybe not yet support useTransition?

thank you for your attention to this!

@ghost
Copy link

ghost commented Dec 3, 2020

Hey @trebor , no worries it is easier to debug but you explained it pretty well. What suspense does is it provides a fallback for our data loading representation. Meaning, if I have a lot of data loading or I even set a timeout, that fallback is there to provide the end user with an understanding that something should be arriving but they won't know until it has loaded. If you want to keep the result visible you need to remove It from outside the wrapper so instead of having it within the suspense wrapper do it as such

<>
  <p>{resource} isPending:{Logic for conditional rendering}</p>
  <SuspenseWrapper>
  {Loading dom element from before}
</SuspenseWrapper>
<>

You will have to figure a way to either bubble up your state or have a persistent storage. There is a package that extends recoil to supply this feature for that and it works pretty great.

@trebor
Copy link
Author

trebor commented Dec 3, 2020

yes, your description of <Suspence> agrees with how i currently understand and use it. my understanding, from reading the Concurrent UI Patterns, is that useTransition is intended to work with <Suspence> to more gracefully handle loading. am i miss reading that?

@ghost
Copy link

ghost commented Dec 7, 2020

@trebor yes within the scope of Suspense you will get a fallback that renders before the content you would like to render. React is trying to push developers to create complex UIs and part of that is handling how you portray your data, especially loading it in for the end-user. What you are doing is fine but if you are looking to render the loading sequence but not wrapped around your number then I suggest using a timeout that shows the loading UI and then disappears after updating your state which will visibly load the number and not seem to disappear for the end user. Other option would be to implement persistent state. Bubbling up or lifting your state up is very harsh on your application as React follows a unidirectional flow and bubbling up is as bad as prop drilling.

@trebor
Copy link
Author

trebor commented Dec 7, 2020

@algorithmhash i really appreciate the herculean effort you making here to help me understand.
that said i remain confused. ultimately i am trying to answer 1 simple yes/no question.

can i use useTransition with recoil and <Suspense> to keep <Suspense> from replacing already rendered DOM elements with the fallback when new data is loaded over already rendered stuff?

YES or NO?

if yes, then i will try to figure out how to do that.
if no, then i will figure out an alternative.

@ghost
Copy link

ghost commented Dec 8, 2020

@trebor it is a no from what I have seen so far. So my suggestion would then be to find some way to incorporate using the suspense correctly in the way you want to, meaning I would use a persistent store and use it to represent that data change outside of the suspense wrapper. Using only suspense for rendering a loading screen after the click.

@trebor
Copy link
Author

trebor commented Dec 8, 2020

got it! thanks so much for the help here!

@ghost
Copy link

ghost commented Dec 8, 2020

@trebor no worries and if you have anymore questions please do not hesitate to ask 😊

@drarmstr
Copy link
Contributor

drarmstr commented Dec 8, 2020

@algorithmhash - Why do you think useTransition() shouldn't be appropriate for this?

@ghost
Copy link

ghost commented Dec 8, 2020

@drarmstr i think it is perfectly fine but they wanted to use the hook in a way to not render what was wrapped in the suspense tags. So my suggestion would be to do some sort of persistent state with the useTransition hook.

@trebor
Copy link
Author

trebor commented Dec 8, 2020

but @algorithmhash isn't the point of useTransition to pair with, and alter the behavior of <Suspense>?

@ghost
Copy link

ghost commented Dec 9, 2020

@trebor this may help in understanding how to achieve, with useTransition and Suspense, what you would like to -> here but to be fair it seems as if something within recoil might be forcing a render. As this is using just hooks with a simple props pass for trickling user data. Maybe figuring out a better way to pass your state around might prove useful to what you are wanting to achieve. Meaning that there might be an issue when using experimental methods with Recoil.

@trebor
Copy link
Author

trebor commented Dec 9, 2020

@algorithmhash, i might direct you my first sentence at the top of this post. :)

i realize that useTransition is bleeding edge, but i'd like to get an early handle on it. this far i've not been able to figure out any combination of parts to get useTransition to work, and i can't find any examples online.

i have seen the example you link to, but i was unable to get it to work with recoil. as far as i can tell, i think you are suggesting i DONT use recoil? in any case, i feel like we are kind of going in circles here.

i can think up plenty of alternatives to solve my specific UI issues, but i feel like we are no closer to answering my question, can we use recoil and useTransition together? AFAICT no.

@drarmstr
Copy link
Contributor

@trebor - It is our intention to be able to use useTransition() with Recoil for this use-case of rendering based on previous state while new state is pending. This is really a duplicate of #290, see some discussion of work-arounds there. Since then we have added support for experimental React Concurrent Mode. However, as you point out, this is all bleeding edge and we just haven't worked through cases with useTransition() yet. If you have specific tests that fail, that may be useful. (cc @davidmccabe)

@trebor
Copy link
Author

trebor commented Dec 12, 2020

@drarmstr this is super helpful thank you! 🙏🏻

i've created what i think to be a minimal example of useTransition, which is not yet delaying the fallback as hoped:

https://codesandbox.io/s/fragrant-smoke-8vxz0?file=/src/UseTransitionExample.js

it uses the latest React experimental 4ead6b530 and Recoile 0.1.2. this use case is somewhat speculative on my part, as i'm not 100% sure if i'm using these things together in the way intended.

i'm now watching #290.

@drarmstr
Copy link
Contributor

drarmstr commented Jan 20, 2021

Thanks @trebor! I updated your example to demonstrate side-by-side comparison of changing React vs Recoil state for useTransition(). It appears this is not yet working for Recoil, something that will have to be fixed.

https://codesandbox.io/s/condescending-leaf-f74zq?file=/src/UseTransitionExample.js

@drarmstr drarmstr changed the title request for example of recoil and useTransition Support useTransition() for showing previous Recoil state with Recoil state changes. Jan 20, 2021
@drarmstr drarmstr added enhancement New feature or request help wanted Extra attention is needed and removed help wanted Extra attention is needed labels Jan 20, 2021
@drarmstr
Copy link
Contributor

We're discussing a possible fix for this now, likely related to a planned optimization as well.

@trebor
Copy link
Author

trebor commented Apr 6, 2021

@drarmstr i wanted to check in on the status of this. for giggles i tried the example with 0.2.0 recoile - no 🍌.

@drarmstr
Copy link
Contributor

drarmstr commented Apr 7, 2021

@drarmstr i wanted to check in on the status of this. for giggles i tried the example with 0.2.0 recoile - no 🍌.

Yeah, not yet with 0.2 or the upcoming memory management work; hopefully after that.

@drarmstr
Copy link
Contributor

Also see #1076

@trebor
Copy link
Author

trebor commented Jan 7, 2022

i'm just dipping back in here to keep things up-to-date. i've got a codesandbox with recoil 0.5.2 and react 18.0.0-rc.0. still not working on the recoil side just yet, but then react 18 ain't ready yet either so no big surprise there. :)

@drarmstr
Copy link
Contributor

drarmstr commented Jan 8, 2022

We did add support in Recoil for useTransition(), but it uses useMutableSource() which was removed from the open-source React release and replaced with useSyncExternalStore(), which doesn't yet support useTransition(). So this is still pending React support.

@drarmstr drarmstr linked a pull request Jan 20, 2022 that will close this issue
@drarmstr drarmstr linked a pull request Jan 28, 2022 that will close this issue
@drarmstr
Copy link
Contributor

#1572 provides experimental hooks for Recoil 0.6 that will enable useTransition() support for Recoil state changes with React 18. Latest React 18 RC is needed as 18.0.0-rc.0 has a bug that has been fixed.

  • useRecoilValue_TRANSITION_SUPPORT_UNSTABLE()
  • useRecoilValueLoadable_TRANSITION_SUPPORT_UNSTABLE()
  • useRecoilState_TRANSITION_SUPPORT_UNSTABLE()

@drarmstr drarmstr self-assigned this Jan 28, 2022
@trebor
Copy link
Author

trebor commented Apr 6, 2022

with the release of react 18 i though i'd check in on this. i've updated the code sandbox:

https://codesandbox.io/s/friendly-bush-jpeu7g?file=/src/UseTransitionExample.js

with react 18.0.0 and recoil 0.7.0 using useRecoilState_TRANSITION_SUPPORT_UNSTABLE.

i'm not seeing the "Suspense Fallback Text..." which i would expect to see 2 seconds after hitting the Increment Recoil Value button - if my understanding of the system is correct.

@drarmstr
Copy link
Contributor

drarmstr commented Apr 6, 2022

@trebor - Took a quick look at your example. The point of transitions is that you shouldn't see the "Suspense Fallback Text...". Instead, the transition will continue to render the contents with the previous state and isPending true until it resolves. It seems to be working: after clicking Increment Recoil Value button it shows isPending as true with no change to the Recoil value, then two seconds later the Recoil value is incremented and isPending is false

@trebor
Copy link
Author

trebor commented Apr 6, 2022

my mistaken understanding as per this description was that the original page content should be displayed only for timeoutMs time. hunting around i found this pr which removes that param. so yes i think recoil is doing the right thing now. thanks so much for the hand holding. :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
3 participants