atomicits/purescript-rnx

Separting react native components/props from thermite

Closed this issue · 13 comments

continuing the conversation from paf31/purescript-thermite#67

@doolse

Thanks for your feedback!

I like the idea of separating thermite from the components/props/styles code. So that nobody else has to create these components code for their library and they can use anything they feel is suitable for their application... either thermite/halogen/custom library.

I will think about it and work on separating thermite code from the components code.

Performance

The following are important for me

  1. Type Safety
  2. Single App State
  3. I like the idea of using lens and prisms for state and action nesting
  4. Performance is very very important

We can always use thermite to create custom components and create elements and use that in the view so it will not have to render the whole tree. I was thinking about using either purescript-signal / observable to subscribe to the changes to the state and use the lens (map the Signal State and create another Signal/Obersable and also dedupe) to get the required state for a custom component.

What do you think about that? any ideas?

I love the way we can compose a view and fold specs in thermite.

The following are important for me

Type Safety

Agreed, top priority. It's a tad annoying that we have to settle for unsafe Array's of props. Halogen has a slightly better way by using row types to specify which properties are allowed, but still not 100% typesafe.

Single App State
I like the idea of using lens and prisms for state and action nesting

I like to keep the state in the parent when it's the parent that's actually in control of that state. Let's take the common TODO list sample app, you have the TaskList component which has a List of Task records which are handled by the Task editing component:

type TaskListState =
  { tasks       :: List Task
  }

type Task =
  { completed :: Boolean
    , description :: String
  }

Which is all very good but let's say you wanted to add a feature to the Task editor which did not affect the TaskList itself, such as a different text editing mode for example:

type Task =
  { completed :: Boolean
    , description :: String
    , weirdEditingMode :: Boolean
  }

If you go with a single state for parent and child, you're forced to leak what should be private details back to the parent. So I think private state and public props/events are what can solve this, however the type safety story for purescript written react components should be better than for javascript based ones.

A single global state has the advantage of having a pure rendering function State -> Html, having all state transitions be explicit and things like time travelling debugging.

Not leaking state to the parent component can be solved by not exporting the constructor and field accessors from the child component module - the only way to modify the child state would be through its state updating function.

A single global state has the advantage of having a pure rendering function State -> Html, having all state transitions be explicit and things like time travelling debugging.

You can conceptually think of React rendering as:
Map Key PrivateState -> State -> (Html, Map Key PrivateState)

Which is still a pure function and can give you time travelling debugging. It also has the advantage that you can render deeply nested parts of the tree without rendering the whole thing, which is good for performance, particularly when you have things like actual mobile native android and ios components backing the component tree and some of which are buggy when they are react native thinks they need to be re-rendered fully (that was my experience, yours may vary!)

The signature I described is pretty much how Halogen handles parent/child relationships but with one key difference, current Halogen gives generic types to the Key and PrivateState, so you're forced to go through all the needless lens/prism traversal just to stay "typesafe". They however are refactoring it as we speak to make it use existential state (aka private state), see: purescript-halogen/purescript-halogen#315

So, the advantage being that you don't have to reroute child event handlers through the parent handler?

This is a big advantage.

I kinda love-hate the explicit wiring in Elm/Thermite/etc. One the one hand, every dependency must be explicitly managed, on the other hand every dependency must be explicitly managed :)

How would child components with private state communicate with a parent? With a global state/handler the parent would react on specific child events. iirc Halogen had some kind of subscriptions, but I never took the time to fully understand them.

The rendering of deeply nested parts could be solved by using reference equality similar to Elm, here is how I did it in a fork of purescript-react.

I kinda love-hate the explicit wiring in Elm/Thermite/etc. One the one hand, every dependency must be explicitly managed, on the other hand every dependency must be explicitly managed :)

I know what you mean :) At first I was excited by the idea but the reality of the boiler-plate quickly dulled my enthusiasm.

How would child components with private state communicate with a parent? With a global state/handler the parent would react on specific child events. iirc Halogen had some kind of subscriptions, but I never took the time to fully understand them.

I would ignore how halogen currently does it's parent/child communication and wait till "halogen-next" is released (which looks pretty soon now..) However I can tell you how I've been handling it, and that is the exact same way you handle communication with the "low-level" html react components like textfields, e.g. pass the state and handlers in props. E.g.

render = input [ value s.text, onChange \v -> dispatch $ SetText v ]

render = child [ state = s.childState, onChildChange (dispatch >>> SetChildState) ]

I'm thinking of forking thermite and tinkering with the task list example app to write it using this style instead just to see what it would look like...

Of course in my child example you wouldn't actually use a prop array, you'd just use a normal typed function to pass them in as props on the react side.

@doolse @pkamenarsky

I might differ here... I like the way the whole app has a single state!

I have been thinking... and Performance can be an issue, but will find out soon... if there is real performance issue when using thermite with React Native in a live project and I am working on one right now.

The way I see it...

  1. There is a single state at the Application level which is a composition of all the states required by all the views
  2. App renders a navigation and based on the route NavigationExperimental uses the renderScene function to render... so it doesn't really have to render the whole tree... it might render or differ rendering all the scenes in its list... So my gut says we shouldn't have any issues with Performance
  3. Even though it adds boiler plate code when we use Lens and Prism... Thermite is much cleaner than the update function / view Action mapping in "PureScript Pux". I love the way we can compose the Specs!! we can also finally separate out the render function from the actions and I love it as its extremely flexible! When using Navigation we need to compose and fold all the actions into the main application Spec and at the same time, we shouldn't merge the render because the scenes are rendered by renderScene custom function passed to the NavigationExpetimental component!
  4. Also, when we use websockets it will be easier to route the messages from a single place and still be able to update the view.

I love thermite and elm architecture! Performance is the only thing I am little worried about... I built a simple test application with Thermite, RNX and NavigationExperimental and everything worked fine!
With multiple screens, nested components etc even passing functions as props down to the children worked fine! use _render to access the render function of the Spec.

I don't see a reason to deviate from thermite!

I would really appreciate if we can close this thread and start a new one and focus on.... How we can separate out the RN components/Props/Styles/API from thermite? Is that really required? as anybody will be able to import RNX and use the components/props/styles and they can ignore the RNX.purs code as none of the other files depend on it.

This will help me move forward with refactoring and finalizing the API and be able to release it as soon as possible.

Appreciate your help!

@doolse Please let me know when you fork thermite, I'd love to see how it turns out!

@sudhirvkumar Would it be possible to depend on purescript-react only? I.e. what part of the functionality is framework-dependent, and would it be possible to keep it as small as possible and release it as a separate package (like purescript-rnx-thermite) while keeping the bulk of the code framework-agnostic?

@sudhirvkumar So I made a pull request ( paf31/purescript-thermite#70 ) which just allows you to pass in the function for wrapping the array of elements. So I would do as @pkamenarsky suggested and create a purescript-rnx-thermite based on that which has a "createClass" function specific to ReactNative (wrap it in a view?).

@doolse @pkamenarsky

Thanks for your suggestions and I have decided to create a separate library purescript-rnx-thermite which will have thermite specific stuff.

@doolse thanks for the PR in thermite that's an elegant way of handling... You are a much better PureScript programmer than me. I am sure to learn a lot from you!

Will close this issue as soon as I separate out the code

Focusing on delivering the current project. So will work on this task later. I guess people can use the other Modules to do what they want. Also I guess it was premature to release it to public while we are still figuring out what and how we want to build this library.

Will continue to build it in public. We will not be releasing unless its stable and feature complete.

as @doolse has built a generic package. I don't see much use in replicating what he has already done. Instead I would like to continue building as whats best for us as a company and if others see a synergy in what we believe in. Then they can use RNX or else they can use @doolse 's library. https://github.com/doolse/purescript-reactnative

So we are going to bake Thermite as an integral part of RNX.

Also, found some interesting ideas in https://github.com/doolse/purescript-reactnative

I might borrow a few ideas from there to make RNX even better.