Open joelburget opened 9 years ago
Can we have a composition tag reactClass_
to bridge the gap between ReactT and ReactClass in an obvious way?:
haskell
div $ do
reactClass someClass
span_ "this is not a class"
^ I see your point about createClass living in the IO monad from trying to use the above example
It looks like the Javascript render
function on each React class is being short-circuited and it needs to work in order for a React class to work when instantiated using createElement(someClass)
. Also, it looks like the state is currently associated with each class, whereas it should be associated with each class instance, which should all get created by React. React already has a place for storing state, so wouldn't it be correct to store the Haskell state as a key inside the JS state?
About to do some more hacking........
Where do animations and transitions fit into Javascript React?
Yeah, I think something like reactClass_
is the right way to approach rendering classes. I might want to reuse locally
.
With 0.13b1 React.createClass
is no longer required, which makes it less awkward to move class creation out of the IO
monad.
I'm currently thinking of classes in react-haskell slightly differently than classes in React proper. Each one is sort of a standalone root like you might see in flux controller-views. The idea being you can isolate different parts of the tree so the whole thing doesn't have to rerender.
So react-haskell is much more prescriptive than regular React because it builds in (soon) controller-view components, transitions, and animations. I think in the future I'd like to separate these different things into their own packages (along with routing, etc), but for now it's most convenient to develop them in the same package.
As far as state vs props, they're operationally the same for top level components, but I think it's slightly clearer to use props.
Was that all reasonable?
I've been working on a branch which uses a reactClass_
as a starting point - it can always be done away with later. The unmonadic class creation helps massively. Although, couldn't we just not use the IO monad, since class creation is effectively instant and doesn't have any side effects (I couldn't think of any)?
But surely you still want to be able to compose them? Does a state change in a component cause all of its parent components to rerender?
I think your approach to these extensions is reasonably sound. With animations, I sort of abandoned them in order to get composing classes working. I'll hopefully be able to bring them back in as an extension in a separate module, but this package. I'll need to do that before submitting a pull request because otherwise my hacking would decrease the functionality in this repo.
couldn't we just not use the IO monad, since class creation is effectively instant and doesn't have any side effects
Yep!
But surely you still want to be able to compose them?
Yes. Here's what I'm thinking. reactClass_
and locally
are almost equivalent, but the former takes a ReactClass
and the latter takes a ReactT
.
With locally
, you're embedding a "dom fragment". It has no embedded knowledge. The class is still responsible for handling transitions and animations.
With reactClass_
, you're embedding a class, which has embedded knowledge. The child class handles its own events like normal. But the parent class also handles events the child class emits. I think this means we need to add another variable to ReactClass
so it has a notion of both internal and external signals - ie those signals which it handles and those it emits. Emitting a signal only happens in response to handling a signal (by the way, I'm being sloppy with the terms "signal" and "transition" - I mean the same thing by both). render
only renders classes that emit Void
- ie never emit signals.
Does a state change in a component cause all of its parent components to rerender?
No. In the formulation I'm picturing a class means two different, but related things:
My observation is that work (events and animations) can often be contained within an abstraction. Like in the map example - we can contain all that work to within the map. I propose intentionally conflating the two meanings of classdom in ReactClass
because more often than not they roughly coincide.
Now, we can limit work to within a class except for when the child emits an event.
Two things I need to think about / come to terms with:
In the flux architecture uni-directional data flow feels like an improvement over plain React. We have something similar-ish at the moment, but only because we can't nest classes (which is definitely a bad thing). React-haskell signals and flux actions are roughly the same. They transition the store (there's only one in react-haskell, it's the top-level state).
Now, it feels like an improvement over the current state of affairs to be able to nest classes, selectively emitting events from child classes, hiding irrelevant state. However, if you squint we've arrived right back at the original React architecture, with props being passed in and state within the class. The difference is we're emitting events rather than taking callbacks. But still, I guess that means we've lost uni-directional data flow, since we need to handle signals at every level in the hierarchy.
I don't think the situation is quite as bad as that, which I can elaborate on later when I've thought about it more and am less tired.
Nesting classes also (if we do it wrong) introduces hidden state, which is definitely not good. One of my design requirements is that you can serialize the entire state of the app. Preferably you can serialize the entire history of the app. Every transition that's ever happened. Would make debugging so much easier.
Note: I've taken a small step away from this world with animations. They're treated as inconsequential state which would be ignored in serialization. I think that's okay.
I took a look at this to get more insight: https://github.com/ianobermiller/nuclearmail/blob/master/src/js/ThreadView.js and made some notes mainly to help my own understanding:
End of notes
If you want to let people use react-haskell in a non-Flux-like way, composing classes, then yes. If you only want to only let people use a Flux-like architecture, then no, unless performance of rerendering the whole React DOM or copying most of a large immutable object is likely to become a problem, in which case yes.
I'd suggest allowing hidden state to exist, and leaving it up to the application as to whether to use it. I'd advocate having a ReactClass that mapped as closely as possible to a JS React Class, and exporting an alternative ReactClass in a module maybe called React.Flux where state
could always be ()
, thereby banning hidden state. An application that used a flux architecture with react-haskell could then more conveniently use React.Flux.ReactClass, not have to worry about state or child handlers, and always be able to serialize its entire state.
This breaks transitions a tiny bit though, because they can no longer just act on the state of the component, instead they have to act the state of some global datastore.
It's currently impossible to render a class from a
ReactT
. This should be possible withlocally
or something.In the best of all worlds you could do something like:
There are a few problems though...
someClass
is notReactT
so we can't use do notation here. Okay, we could useRebindableSyntax
to make this possible, but I think that's a bad idea. Instead, why not extendlocally
to take either aReactT
or aReactClass
.IORef
, but that's an implementation detail, not a means of abstraction. Maybe we don't need classes. Maybe we can figure out a clearer meaning for them.createClass
also feels really weird. It's stateful in an unclear way and lives in the IO monad.