[SOLVED] How to handle async (i.e. Aff) in react-hooks component?

I have a Component that needs to fetch data from server to have stuff to render. So I want to execute a Ajax.get Ajax.string "/some/url" in the component body.

Now, the problem is that AJAX libs return Aff, whereas the component body is Effect and as turns out it is impossible to get Aff results in Effect. And the Component type is aliased to Effect and not to Aff.

So I am not sure how to handle that correctly.


So, to have some code as an example, given this:

module Main where

import Prelude

import Data.Maybe (Maybe(..))
import Effect (Effect)
import Effect.Aff (Aff, delay, Milliseconds(..))
import Effect.Exception (throw)
import React.Basic.DOM as R
import React.Basic.DOM.Client (createRoot, renderRoot)
import React.Basic.Hooks (Component, component, useState, (/\))
import React.Basic.Hooks as React
import Web.DOM.NonElementParentNode (getElementById)
import Web.HTML (window)
import Web.HTML.HTMLDocument (toNonElementParentNode)
import Web.HTML.Window (document)

main :: Effect Unit
main = do
  doc <- document =<< window
  root <- getElementById "root" $ toNonElementParentNode doc
  case root of
    Nothing -> throw "Could not find root."
    Just container -> do
      reactRoot <- createRoot container
      app <- myComponent
      renderRoot reactRoot (app {})

myAffCount :: Aff Int
myAffCount = do
  delay $ Milliseconds 1000.0
  pure 7

myComponent :: Component {}
myComponent =
  component "Label" \_ -> React.do
    count /\ setCount <- useState 0
    pure $ R.label {children: [R.text (show count)]}

How do I make myComponent get the number from myAffCount to render, otherwise render 0?

I found two examples in “purescript-cookbook”, which let me come up with the solution. This one is a little more involved, and that one is a bit simpler.

The solution is putting Aff-code into the hook useAff. useAff gives type Maybe result, and it immediately returns Nothing. Inside your component you branch rendering based on whether it’s Nothing (which it will be immediately on creation). At some point your Aff code finishes with an actual result, making your rendering code re-run.

Solution for the example code below. It’s interesting to note that no other hooks being used here besides useAff.

module Main where

import Prelude

import Data.Maybe (Maybe(..), fromMaybe)
import Effect (Effect)
import Effect.Aff (Aff, delay, Milliseconds(..))
import Effect.Exception (throw)
import React.Basic.DOM as R
import React.Basic.DOM.Client (createRoot, renderRoot)
import React.Basic.Hooks (Component, component)
import React.Basic.Hooks as React
import React.Basic.Hooks.Aff (useAff)
import Web.DOM.NonElementParentNode (getElementById)
import Web.HTML (window)
import Web.HTML.HTMLDocument (toNonElementParentNode)
import Web.HTML.Window (document)

main :: Effect Unit
main = do
  doc <- document =<< window
  root <- getElementById "root" $ toNonElementParentNode doc
  case root of
    Nothing -> throw "Could not find root."
    Just container -> do
      reactRoot <- createRoot container
      app <- myComponent
      renderRoot reactRoot (app {})

myAffCount :: Aff Int
myAffCount = do
  delay $ Milliseconds 1000.0
  pure 7

myComponent :: Component {}
myComponent =
  component "Label" \_ -> React.do
    affState :: Maybe Int <- useAff unit myAffCount
    let count = fromMaybe 0 affState
    pure $ R.label {children: [R.text (show count)]}
1 Like