Skip to content

Question: fetching initial data and putting it to state #32

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

Closed
mananamenos opened this issue Jun 22, 2020 · 4 comments
Closed

Question: fetching initial data and putting it to state #32

mananamenos opened this issue Jun 22, 2020 · 4 comments

Comments

@mananamenos
Copy link

mananamenos commented Jun 22, 2020

Hi, I want to fetch some data and put it in the state. With useAff I would do something like this:

mkA :: Component Unit
mkA = do
  b <- mkB
  component "A" \props -> React.do
    res <- useAff unit (runExceptT fetchInitialData)
    pure
      $ case res of
          Nothing -> R.text "..."
          Just (Left e) -> R.text "err"
          Just (Right foo) -> b foo

mkB :: Component (Array Foo)
mkB = do
  component "B" \foo -> React.do
    s /\ setS <- useState foo
    pure $ R.text "b"

My question is: How could I do the same in one component? I was thinking of using useEffectOnce with launchAff_? Or is this not recommended?

Also, the comment of useEffectOnce function says:

-- | Like `useEffect`, but the effect is only performed a single time per component
-- | instance. Prefer `useEffect` with a proper dependency list whenever possible!

I don't understand why is useEffect preferable over useEffectOnce, when I want the effect to be performed only once? Isn't useEffectOnce's purpose just that, so that the user wouldn't need to pass a dummy param like unit or [] as an argument?

Thanks!

@maddie927
Copy link
Member

maddie927 commented Jun 22, 2020

Hello! (I edited your post just to add syntax highlighting)

Your code seems fine to me. You're using that setS function, right? This is one case where you can't combine into a single component as-is, simply due to the need to pattern match res to get the foo out of it. In the future when the suspense api is more usable you could do that like this:

mkA :: Component Unit
mkA = do
  component "A" \props -> React.do
    foo <- suspend (runExceptT fetchInitialData)
    s /\ setS <- useState foo
    pure $ R.text "b"

But it's a beta feature that isn't well documented yet (I'm working on it, but also it's still beta in React itself).

As for useEffectOnce + launchAff_, I don't see how that would allow a single component. You'd still have to pattern match res to get foo, so you'd still need your useState in a separate component. This makes me wonder if you actually need the useState. Is setS being used? If not, the useState is redundant -- useAff is already holding the state.

I would also recommend against replacing useAff with launchAff_. useAff cancels stale requests and guarantees the result you see in your render function is always in sync with the provided key. If you do have an advanced case useAff can't handle, I recommend using useAff's implementation as a starting point to avoid race conditions.


Ok, useEffect vs useEffectOnce. useEffectOnce ties your effect to the lifecycle of the component, because the "once" part means "once per mounted instance of this component". There are a few edge cases where this is necessary, but in the vast majority of cases this is a bug. Effects (including useAff) nearly always depend on parameters: a key, some filters, a refresh button, etc. And even if some effect truly has no input, useEffect unit fits that model really well -- unit is the "nothing" input. Because of this I'd argue that while useEffect unit and useEffectOnce happen to behave the same, conceptually they are quite different. I added useEffectOnce along with useEffectAlways because the latter is more difficult to implement and they are still occasionally useful for signaling particular edge cases, but yeah, don't recommend them generally. That's also why there are no useAffOnce or useAffAlways functions -- there's nearly always a relevant key.


If any of that is confusing or doesn't fit your use case please let me know. I'm working on updated documentation/guides and want to make sure it's as clear and useful as possible. 🙂

@mananamenos
Copy link
Author

mananamenos commented Jun 23, 2020

Hello. Thank you very much for your detailed answers.

Sorry, I did not put an appropriate example where it would be clear, that the fetched data should be put in the state, so that later, clicks and other events could mutate that state. For example, when deleting a list item, I would modify the state, which holds that list, instead refetching the list again.


As for useEffectOnce + launchAff_ I had in mind something like this:

mkA :: Component Unit
mkA = do
  component "A" \props -> React.do
    foo /\ setFoo <- useState ([] :: Array String)
    React.useEffect unit do
      launchAff_ $
        runExceptT Api.getFoo >>= case _ of
          Left e -> liftEffect $ log $ "err.."
          Right r -> liftEffect $ setFoo (const r)
      pure $ pure unit
    pure
      $ case foo of
        [] -> R.text "..."
        _ -> R.text $ show foo

There are a few edge cases where this is necessary, but in the vast majority of cases this is a bug
I don't think I understand what you mean with this. Is it, that users use useEffectOnce when they actually should use useEffect because there actually are dependencies that users have missed on?

@maddie927
Copy link
Member

Yes, there's usually a dependency to track. In your example there isn't, but if you ever added one there would be two potential bugs:

  1. Using a dependency without replacing unit with the dep as well, which would result in the component not re-fetching data when it should.
  2. Using a dependency and replacing unit, but otherwise not changing this code, which would result in race conditions. If the dep changes twice in a short period of time, nothing guarantees the completing requests are handled in the correct order. You'll also get react warnings if this component unmounts while a request is in progress. useAff (or copying its implementation, the bits dealing with fibers) handles these edge cases.

@mananamenos
Copy link
Author

Thank you.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants