[SOLVED] React Hooks: how to pass data from Child up to Parent?

For simplicity, suppose I have a parentComponent with a label with text “foo” and a childComponent that renders a button clicking which should make parentComponent change “foo” to “bar”.

In raw JS it’s done by obtaining a setText via hook call _ /\ setText <- useState "foo" in the parent, then passing the setText down to the child, so it can call it when button is clicked.

Sounds simple. But the problem is that instantiating childComponent is “effectful”, and so has to be done outside the React.do block. But setText first appears inside the React.do, where I can’t seem to be able to do "Effect"ful actions!

So how do I do that?

Here’s a code for the example:

module Main where

import Prelude

import Data.Maybe (Maybe(..))
import Data.Tuple.Nested ((/\))
import Effect (Effect)
import Effect.Exception (throw)
import React.Basic.DOM as R
import React.Basic.DOM.Client (createRoot, renderRoot)
import React.Basic.Events (handler_)
import React.Basic.Hooks (Component, component, useState')
import React.Basic.Hooks as React
import Web.HTML.HTMLDocument (toNonElementParentNode)
import Web.DOM.NonElementParentNode (getElementById)
import Web.HTML (window)
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 <- parentComponent
      renderRoot reactRoot (app {})

parentComponent :: Component {}
parentComponent = do
  component "Parent" \_ -> React.do
    text /\ setText <- useState' "foo"
    ch <- childComponent setText -- ERROR: can't do Effect, neither can move outside as `setText` didn't exist there
    pure $ R.div
      { children: [ R.label { children: [ R.text text ] }
                  , ch ] }

childComponent :: (String -> Effect Unit) -> Component {}
childComponent setText = component "Child" \_ -> React.do
  pure $ R.button
    { onClick: handler_ $ setText "bar"
    , children: [ R.text "Change Text to bar" ]
    }

I realized, my problem was complete misunderstanding of what goes where.

In React Hooks a Component creation is a two stage process. You don’t just take a component function and pass it a param, you first extract the “component creation” out of your Component, and only then you actually create a component and optionally pass it parameters.

So the solution for the example code:

module Main where

import Prelude

import Data.Maybe (Maybe(..))
import Data.Tuple.Nested ((/\))
import Effect (Effect)
import Effect.Exception (throw)
import React.Basic.DOM as R
import React.Basic.DOM.Client (createRoot, renderRoot)
import React.Basic.Events (handler_)
import React.Basic.Hooks (Component, component, useState')
import React.Basic.Hooks as React
import Web.HTML.HTMLDocument (toNonElementParentNode)
import Web.DOM.NonElementParentNode (getElementById)
import Web.HTML (window)
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 <- parentComponent
      renderRoot reactRoot (app {})

parentComponent :: Component {}
parentComponent = do
  ch <- childComponent             -- stage 1
  component "Parent" \_ -> React.do
    text /\ setText <- useState' "foo"
    pure $ R.div
      { children: [ R.label { children: [ R.text text ] }
                  , ch setText ] } -- stage 2

childComponent ::  Component (String -> Effect Unit)
childComponent = component "Child" \setText -> React.do
  pure $ R.button
    { onClick: handler_ $ setText "bar"
    , children: [ R.text "Change Text to bar" ]
    }

Sorry, wrong thread, removed the post.