paf31 / purescript-thermite

A simple PureScript wrapper for React
MIT License
350 stars 55 forks source link

Nesting Specs #54

Open spencerjanssen opened 8 years ago

spencerjanssen commented 8 years ago

Thermite has the concept of sequencing Specs via the Semigroup instance, but sometimes you want to nest the markup of a Spec inside another. Lately I've been using this helper function:

wrap :: forall eff state props action.
    Spec eff state props action ->
    Spec eff state props action ->
    Spec eff state props action
wrap parent child = simpleSpec pa rn 
 where
    pa a p st k = do
        view _performAction parent a p st k
        view _performAction child a p st k
    rn k p st children = view _render parent k p st (view _render child k p st children)

At this point, I'm not sure whether this re-appropriation of the children argument is elegant or hacky. Does it make sense to add a function like this to Thermite? I'm definitely open to better names as well.

paf31 commented 8 years ago

Hmm, that's interesting. I expected users would pass a Spec for any inner component as a function argument, but I suppose it makes sense to use the children property instead. I suppose it is a little hacky, but then that argument has always been undocumented, so we can call this the default going forward, and document that any React-provided children will be passed in at the top level.

I think the name wrap might be used though.

paf31 commented 8 years ago

Another question is: should the children be passed as a singleton array? Or even pass an array of child Specs?

pkamenarsky commented 8 years ago

This is what I came up with:

render dispatch _ state _ = 
   [ R.div [...] [ view T._render childSpec dispatch {} state [] ]

childSpec can of course be focused to use some substate/action. Is there a more idiomatic way to do that?

Furthermore, what would props be used for in Thermite? When using focused specs there's only one global state anyway, or am I missing something?

pkamenarsky commented 8 years ago

I'm not sure how useful that would be, but if Render was a Reader monad, it would be possible to pass down dispatch/state to child specs implicitly.

paf31 commented 8 years ago

@pkamenarsky I think focusing would use a separate call to focusState etc.

Props are used at the top-level when the full component is integrated into a larger React application.

I'm interested in the Reader approach, for sure, but it adds a cognitive overhead for new users which I'd like to avoid. Right now, all you need to get started is functions and data types (I consider the lens stuff to be an advanced feature, but even then, you don't need to know about type classes or monads to use it).

pkamenarsky commented 8 years ago

@paf31 Yes, focusing is a separate call (i.e. childSpec = focus ...). What I meant though is that it would be cool to be able to nest components the same way other elements are nested:

[ R.div [...] [ chidComponent [...] [...] ]

without having to call render explicitly. But I guess that would only be possible with the Reader approach - and I agree, the mental overhead probably doesn't justify that.

re props - so if the whole application is written in Thermite, props are basically useless?

paf31 commented 8 years ago

Pretty much, on the props thing.

teh commented 8 years ago

I have an almost identical function

nestSpec :: forall eff state props action. T.Spec eff state props action -> T.Spec eff state props action -> T.Spec eff state props action
nestSpec parent child = T.simpleSpec performAction render
 where
   performAction a p st = do
     view T._performAction parent a p st
     view T._performAction child a p st
   render k p st children = view T._render parent k p st (view T._render child k p st children)

Would you be open for a PR?