Skip to content

v4 API#58

Merged
maddie927 merged 30 commits into
purescript-react:masterfrom
maddie927:master
Nov 7, 2018
Merged

v4 API#58
maddie927 merged 30 commits into
purescript-react:masterfrom
maddie927:master

Conversation

@maddie927
Copy link
Copy Markdown
Member

@maddie927 maddie927 commented Oct 2, 2018

These changes address a few things:

  • Allow typeclasses in component props or state
  • Allow any types as props and state, not just records
  • Support lifecycle functions such as shouldUpdate and willUnmount
  • Encourage (but not require) an Action-like API for component state

I think this is ready enough for feedback. The API is extremely reason-react inspired (Reason has similar language requirements/constraints, the API is very close to vanilla React's besides the concept of Actions/update/reducer because Reason and PureScript have more powerful pattern matching abilities).

There are still a few bits I'm not super happy with -- I'll annotate the code to point them out.

This is a pretty significant change over react-basic 2.0. It's now less "basic" in the "implement a small subset of vanilla React in PS, where 'basic' means small" and instead more along the lines of "implement a thin but opinionated wrapper around React, where 'basic' means encourage correctness without sacrificing performance or type complexity". 🙂

Remaining work:

  • Review API, feedback, etc
  • Write documentation
  • Update the Lumi component library to use this branch to see how it feels and compare performance

Fixes #52
Fixes #53

@maddie927 maddie927 self-assigned this Oct 2, 2018
@maddie927 maddie927 force-pushed the master branch 4 times, most recently from f1778ce to 1876e68 Compare October 5, 2018 21:15
Comment thread examples/component/src/Container.purs Outdated
}

component :: StatelessComponent
component = createStatelessComponent "Container"
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Example of a stateless component. API is not that different. Note the pre-applied prop: render = unit # .... Non-record prop and state types are now allowed, and since this component has no props it's safe to pre-apply one to avoid work.

It's also worth noting that because render will usually have the type props -> JSX the only advantage of defining a "stateless component" is to create the named wrapper component in React's dev tools. If this isn't a concern, just export a plain props -> JSX render function without using make or makeStateless.

Comment thread examples/component/src/Main.purs Outdated
Nothing -> throw "Container element not found."
Just c ->
let app = element Container.component {}
let app = Container.render
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

element is now only used for rendering ReactComponents, i.e. a component imported from JS. react-basic's Component type is not a JS-friendly component and make function applies React.createElement automatically. Before this PR is complete we'll add a "to-JS" helper for JS-friendly exports.

Copy link
Copy Markdown
Member Author

@maddie927 maddie927 Oct 6, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added toReactComponent. It ends up just being an unsafe coerce, but it’s still an important distinction because ReactComponent reference equality matters while props -> JSX reference equality does not (meaning no type class wrappers should be present wherever toReactComponent is used)

UpdateAndSideEffects
self.state { on = not self.state.on }
\self -> do
log $ "nextState: " <> show self.state
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

State updates are now handled by the update function, which takes Self and an action argument. There are no restrictions on the action type used, but a component-specific sum-type is recommended, as it summarizes the capabilities of the component well.

The update function should return the desired state change, including an optional effect to perform once the state change is complete. This state change is described using StateUpdate.

This function is analogous to reason-react's reducer function.

{ onClick: Events.handler_ do
setStateThen (\s -> s { on = not s.on }) \nextState -> do
log $ "nextState: " <> show nextState
{ onClick: Events.handler_ do self.send Toggle
Copy link
Copy Markdown
Member Author

@maddie927 maddie927 Oct 5, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Component interactivity is driven by the send function. Calls to send trigger the previously described update process. Event handlers are still Effects, so you can put complicated effects here, but when it comes time to effect a change on the component itself it needs to be distilled into a single packet of changes.

For example, a SearchableTable component might have an onChange handler on a text field. It could read the field value, initiate a request, and as that request completes call send (SearchResults results). However, a better approach would be to read the field value and immediately call send (OnSearch searchTerm), and handle that action in update using SideEffects \self -> do [network >>= (self.send >>> SearchResults)]. This simplifies the render function and describes more of the component behavior in a way that's isolated from the DOM.

Comment thread src/React/Basic.js Outdated
};
var defaultRender = function() {
return false;
};
Copy link
Copy Markdown
Member Author

@maddie927 maddie927 Oct 5, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These default implementations keep the PureScript types honest for ComponentSpec fields which are not overridden.

Comment thread src/React/Basic.purs Outdated
Comment thread src/React/Basic.purs Outdated

type Component = forall props state action. ComponentSpec props state action

type StatelessComponent = forall props. ComponentSpec props Void Void
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are here for convenience when writing components, but otherwise don't do much

Comment thread src/React/Basic.purs Outdated
Comment thread src/React/Basic.purs

-- | Apply a React key to a sub-tree.
keyed :: String -> JSX -> JSX
keyed = runFn2 keyed_
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

element and elementKeyed are now specific to JavaScript React components. keyed nests any JSX in a keyed Fragment, allowing a parent to specify keys without altering the simplified react-basic types.

@maddie927 maddie927 changed the title API experiment Potential v3 API Oct 5, 2018
@maddie927 maddie927 force-pushed the master branch 3 times, most recently from 68e97f0 to 10c3116 Compare October 8, 2018 17:44
@maddie927 maddie927 changed the title Potential v3 API Potential v4 API Oct 8, 2018
@maddie927
Copy link
Copy Markdown
Member Author

maddie927 commented Oct 15, 2018

@justinwoo @paluh @zudov Not sure which of you are actively using this library, but we're curious what your thoughts are on these changes if you are using it. (I haven't written a readme update yet, but there is a React.Basic.Compat module as well for delaying the upgrade of particular components.

@justinwoo
Copy link
Copy Markdown
Contributor

I'm starting to use more of my own layer on top that just works via a version of the regular React component API, so I won't have much to say about this immediately. Hopefully I will get around to reading through this sometime.

@maddie927
Copy link
Copy Markdown
Member Author

Ah, @f-f too, thanks!

@maddie927
Copy link
Copy Markdown
Member Author

Neat! I'd love to see how it goes (and hopefully if I push a couple more tweaks it won't be too confusing -- let me know if you have questions). Also, while you can port v2/3 to this mechanically, it feels a lot better if you give the actions are thought out a bit (send/capture work better, update is more descriptive, etc).

@maddie927
Copy link
Copy Markdown
Member Author

There is currently a compiler issue/limitation which shows up occasionally when using capture in a where function. You can work around it by annotating the function (for render: Self Props State Action -> JSX). It's one of the remaining things I want to fix.

@maddie927
Copy link
Copy Markdown
Member Author

Refactored Self a bit to avoid the unexpected type error sometimes seen with capture. API's nearly the same, but a few functions which were found on Self before are now just normal, module-level functions with Self as a parameter. These changes allowed for some minor performance improvements as well :)

@jgoux
Copy link
Copy Markdown

jgoux commented Oct 25, 2018

Now that React disclosed hooks (https://reactjs.org/docs/hooks-reference.html), what do you think about them and how could they be integrated with PureScript?

@maddie927
Copy link
Copy Markdown
Member Author

They're somewhat monadic which is interesting. The implementation and usage in JS is concerning, but that's already the case for a lot of JS anyway. Maybe it could be exposed via PureScript in a safer way, but whether it's a nicer API would take some time and experimentation to discover. It's also just a proposal at this time, so it's not ready to build on top of anyway.

tl;dr, not sure yet

@jgoux
Copy link
Copy Markdown

jgoux commented Oct 29, 2018

Thanks for your answer!

The experimentation has begun with Reason : https://twitter.com/jaredforsyth/status/1056902656197828608 🎊

EDIT : Another really interesting case (I totally love this one!) : https://paulgray.net/an-alternative-design-for-hooks/

@maddie927
Copy link
Copy Markdown
Member Author

@jgoux So, I took a stab at it: Counter example with hooks

I actually am liking it.. 🤔

I saw that alt design too, and the interesting bit is that it's no different in PS.. do-syntax reads like the React proposal but compiles to the alt proposal 😉

@maddie927
Copy link
Copy Markdown
Member Author

It feels much more flexible and opt-in (to state and lifecycles), and makes it easier to build complicated state helpers that manage their own subscriptions outside the component.

@jgoux
Copy link
Copy Markdown

jgoux commented Nov 1, 2018

Wow this API seems awesome!
I think I might try using it on a small project !

@maddie927
Copy link
Copy Markdown
Member Author

I removed Applicative Render -- that inner props -> do ... being passed to component is props -> Render JSX. Render is a newtype around Effect with its constructor hidden, and all the hooks run in Render. Removing pure makes it harder to do conditional logic, but it's still possible.. so maybe it isn't worth the inconvenience of not having pure.. I can add it back

@maddie927
Copy link
Copy Markdown
Member Author

Actually, yeah.. it'd make it significantly harder to write custom state/effect logic without pure undoing that haha

@maddie927
Copy link
Copy Markdown
Member Author

Made a separate PR to discuss hooks: maddie927#1

Comment thread examples/counter/src/Counter.purs Outdated
initialState =
{ counter: 0
}
initialState = { counter: 0, dummy: 0 }
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dummy?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops, will remove

@maddie927 maddie927 changed the title Potential v4 API v4 API Nov 5, 2018
@maddie927 maddie927 merged commit 90ccad9 into purescript-react:master Nov 7, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Performance and potential changes to allow typeclasses on props Too easy to create infinite loops

6 participants