purescript-concur / purescript-concur-react

Concur UI Framework for Purescript
https://purescript-concur.github.io/purescript-concur-react
MIT License
268 stars 17 forks source link

Creating and rendering into a canvas #39

Open justinlovinger opened 4 years ago

justinlovinger commented 4 years ago

I'm trying to create a canvas element containing an animation, but I'm not sure how to compose the canvas itself with the render Effect.

With React, you can use a componentDidMount callback to render on a canvas, when the canvas is created in the dom. However, I don't see anything like onComponentDidMount.

Currently, my solution looks like

canvasId ← liftEffect random
myCanvas ← canvas [ _id $ show canvasId, onClick ] []
_ ← liftEffect $ runCanvas canvasId
canvas [ _id $ show canvasId] []

This solution is not ideal for a few reasons. The onClick is just a placeholder to get the runCanvas effect to run, and ending with a second canvas feels like a hack.

I have also considered starting an asyncronous effect with a wait loop, before calling the first canvas, but then I have an unnecessary wait loop and might leave orphan scripts.

I'm not sure how to bundle a canvas element with it's corresponding render effect, without resorting to a workaround.

ajnsit commented 4 years ago

Unless I am misunderstanding your question, this sort of parallel UI composition is already well supported by Concur. Have you tried using <|> to compose the runCanvas in parallel with the canvas UI element, and not letting the runCanvas end by following it with empty -

do
  canvasId ← liftEffect random
  canvas [ _id $ show canvasId ] []
    <|> (liftEffect (runCanvas canvasId) *> empty)
justinlovinger commented 4 years ago

Composing in parallel results in runCanvas running before the canvas element exists, requiring an asyncronous wait loop. It is another workaround, but it still isn't as clean as something like an explicit onMount callback.

ajnsit commented 4 years ago

Ah I see.

Synchronous actions are run "in-place" before populating the view, but asynchronous actions are run after the view is populated and stable, equivalent to doing it in componentDidMount. So something like this might do the trick -

do
  canvasId ← liftEffect random
  canvas [ _id $ show canvasId ] []
    <|> liftAff (liftEffect (runCanvas canvasId) *> empty)

Note that here we have pushed the empty inside the Aff. This makes sure that the Aff is not resolved synchronously.

justinlovinger commented 4 years ago

Hmm, using liftAff like that results in a runtime error:

Aff failed - parcelRequire<["output/Effect.Exception/foreign.js"]</exports.error@http://localhost:1234/e31bb0bc.js:19985:10
parcelRequire<["output/Effect.Aff/index.js"]<@http://localhost:1234/e31bb0bc.js:40984:73
newRequire@http://localhost:1234/e31bb0bc.js:47:24
localRequire@http://localhost:1234/e31bb0bc.js:53:14
parcelRequire<["output/Concur.Core.Types/index.js"]<@http://localhost:1234/e31bb0bc.js:42723:25
newRequire@http://localhost:1234/e31bb0bc.js:47:24
localRequire@http://localhost:1234/e31bb0bc.js:53:14
parcelRequire<["output/Component.Indicator/index.js"]<@http://localhost:1234/e31bb0bc.js:46325:32
newRequire@http://localhost:1234/e31bb0bc.js:47:24
localRequire@http://localhost:1234/e31bb0bc.js:53:14
parcelRequire<["output/Component.App/index.js"]<@http://localhost:1234/e31bb0bc.js:61772:34
newRequire@http://localhost:1234/e31bb0bc.js:47:24
localRequire@http://localhost:1234/e31bb0bc.js:53:14
parcelRequire<["output/Main/index.js"]<@http://localhost:1234/e31bb0bc.js:95989:28
newRequire@http://localhost:1234/e31bb0bc.js:47:24
localRequire@http://localhost:1234/e31bb0bc.js:53:14
parcelRequire<["index.js"]<@http://localhost:1234/e31bb0bc.js:96008:36
newRequire@http://localhost:1234/e31bb0bc.js:47:24
parcelRequire<@http://localhost:1234/e31bb0bc.js:81:17
@http://localhost:1234/e31bb0bc.js:120:3

Although, even if the runtime error is fixed, it still feels more like a workaround than intended behaviour. It's not clear from the code that liftAff means "wait for the component to mount, then do this", and I think it is a common enough usecase to get proper support.

ajnsit commented 4 years ago

Hmm, that seems like a bug. I'm looking at it.