Skip to content

Instantly share code, notes, and snippets.

@i-am-the-slime
Created August 19, 2021 12:28
Show Gist options
  • Save i-am-the-slime/eb0f9168e17277b4ff37de25dfe6816e to your computer and use it in GitHub Desktop.
Save i-am-the-slime/eb0f9168e17277b4ff37de25dfe6816e to your computer and use it in GitHub Desktop.
module Facebook.AdInfo.List.Component where
import Yoga.Prelude.View hiding (top)
import Data.Function.Uncurried (mkFn2)
import Data.Newtype (un)
import Effect.Aff (Aff, Error, error, forkAff, killFiber, launchAff)
import Effect.Aff.Extra (safelyLaunchAff_)
import Facebook.AdInfo.Component.Types (DetailVisibility(..))
import Facebook.AdInfo.Component.Types as AdInfo
import Foreign.Object as Object
import LazyComponents (mkAdInfoLazy, renderWhenLoaded)
import Murmuras.Insights.API.AdInfo.Queries (AdSummary)
import Murmuras.Insights.API.AdInfo.Types (AdId(..), GQLAdInfo)
import Network.RemoteData (RemoteData)
import Network.RemoteData as RD
import React.Basic.DOM as R
import React.Basic.Hooks as React
import React.Virtuoso (virtuosoWithData)
type Props
= { adSummaries ∷ Array AdSummary
, keyword ∷ String
, loadDetails ∷ AdId → Aff (Either Error GQLAdInfo)
, spinner ∷ JSX
}
modifyRef ∷ ∀ a. (a → a) → Ref a → Effect Unit
modifyRef fn ref = do
React.readRef ref >>= React.writeRef ref <<< fn
mkFacebookAdResultsList ∷ React.Component Props
mkFacebookAdResultsList = do
React.component "FacebookAdResultsList" \props → React.do
let { adSummaries, keyword, loadDetails } = props
lazyAdInfoComponent /\ setAdInfo ← React.useState' (RD.NotAsked ∷ _ _ (AdInfo.Props → JSX))
expandedViews /\ updateExpandedViews ← React.useState Object.empty
adDetails /\ updateAdDetails ← React.useState Object.empty
runningFibersRef ← React.useRef Object.empty
lastFiberIdRef ← React.useRef 0
let collapseAllViews = do updateExpandedViews (const Object.empty)
let
killInFlightFibers = do
runningFibers ← React.readRef runningFibersRef
safelyLaunchAff_ do
for_ (Object.values runningFibers) (killFiber (error "Component unmounted, fiber not needed anymore"))
-- Cancel any pending fibers on component unmount
React.useEffectOnce (pure killInFlightFibers)
-- A keyword change is like a reset for the list view
React.useEffect keyword do
-- We don't need ad details for the old keyword
killInFlightFibers
React.writeRef runningFibersRef Object.empty
-- Free the memory holding the info about which old ad ids are expanded
collapseAllViews
mempty
let
lookupExpandedView adId =
expandedViews # Object.lookup (un AdId adId) # fromMaybe DetailCollapsed
hideAdDetails adId = updateExpandedViews $ Object.insert (un AdId adId) DetailCollapsed
showAdDetails adId = updateExpandedViews $ Object.insert (un AdId adId) DetailVisible
lookupAdDetails ∷ AdId → (RemoteData Error GQLAdInfo)
lookupAdDetails adId = adDetails # Object.lookup (un AdId adId) # fromMaybe RD.NotAsked
loadAdDetails ∷ AdId → Effect Unit
loadAdDetails adId = do
if (adDetails # Object.member (un AdId adId)) then
mempty
else do
updateAdDetails $ Object.insert (un AdId adId) RD.Loading
-- [TODO] Cancel affs?
fiberId ← React.readRef lastFiberIdRef <#> (_ + 1)
React.writeRef lastFiberIdRef fiberId
fiber ←
launchAff do
forkAff do
result ← loadDetails adId <#> RD.fromEither
updateAdDetails (Object.insert (un AdId adId) result) # liftEffect
runningFibersRef # modifyRef (Object.delete (show fiberId)) # liftEffect
runningFibersRef # modifyRef (Object.insert (show fiberId) fiber)
-- Lazy load the component
React.useEffectOnce do
when (RD.isNotAsked lazyAdInfoComponent) do
safelyLaunchAff_ do
setAdInfo RD.Loading # liftEffect
adInfo ← mkAdInfoLazy
setAdInfo (RD.Success adInfo) # liftEffect
mempty
let
renderItem ∷ (AdInfo.Props → JSX) → AdSummary → JSX
renderItem adInfoComponent ad@{ id } =
adInfoComponent
{ adInfo: ad
, rdAdDetails: lookupAdDetails id
, showAdDetails: showAdDetails id *> loadAdDetails id
, hideAdDetails: hideAdDetails id
, detailVisibility: lookupExpandedView id
, keyword
}
adInfoListView adInfoComponent =
fragment
[ React.element virtuosoWithData
{ useWindowScroll: true
, data: adSummaries
, itemContent:
mkFn2 \index item → do
let key = keyword <> "-" <> show index
R.div'
</ { key }
/>
[ renderItem adInfoComponent item
]
}
, props.spinner
]
pure
$ lazyAdInfoComponent
# renderWhenLoaded adInfoListView
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment