Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add an example which includes an external component #2

Open
paf31 opened this issue Jan 6, 2018 · 14 comments
Open

Add an example which includes an external component #2

paf31 opened this issue Jan 6, 2018 · 14 comments

Comments

@paf31
Copy link
Contributor

paf31 commented Jan 6, 2018

It would be good to demonstrate that we can use other components easily.

@megamaddu
Copy link
Member

Would this include to/from functions for purescript-react's ReactComponent type? I'm curious if you already have something in mind for the non-basic cases. I've been thinking about something similar to this but as convenience functions for common patterns in or on top of purescript-react so it's easy to reach for a lifecycle function when you need it. I really like the props setup going on in this library though.

@megamaddu
Copy link
Member

Specifically, I was thinking about stealing reason-react's "reducer" component (very similar to elm/pux/redux, but using component state).

Examples:
createStatefulComponent (\props -> initialState) reducer render
createStatefulComponentWithInit InitAction (\props -> initialState) reducer render (componentDidMount)
createFormContainer ??? (don't know how this would work yet but I want it 🙂)

@paf31
Copy link
Contributor Author

paf31 commented Jan 10, 2018

I was thinking more about components from NPM. I haven't really thought too much about adapters, but I think I would prefer for them to be in a separate library. I considered going via purescript-react, but I wanted as few dependencies as possible and the freedom to try different APIs out easily.

This library is pretty much tailor-fit for our use cases right now, but if the need for lifecycle methods comes up, we'll have to find a way to include them, hopefully without complicating the API.

Yes, I expect a Redux/ElmArch layer on top of this would be quite useful.

@paf31 paf31 closed this as completed Feb 21, 2018
@jgoux
Copy link

jgoux commented Apr 14, 2018

Hello,

were these examples ever added?

I'd be interested in wrapping external components 🌯

@paf31
Copy link
Contributor Author

paf31 commented Apr 16, 2018

Not yet, although they basically amount to using the FFI to pull in the component class as a value and then using it as normal.

@megamaddu
Copy link
Member

megamaddu commented Apr 16, 2018

Example for (simplified) React Router Route component:

type RouteProps =
  { path :: String
  , exact :: Boolean
  , render :: MatchedRouteProps -> JSX
  }

foreign import route :: ReactComponent RouteProps

@i-am-the-slime
Copy link
Member

But what would the js file look like?

@megamaddu
Copy link
Member

Something like this:

"use strict";

var RR = require("react-router");

exports.route = RR.Route;

@i-am-the-slime
Copy link
Member

Thank you very much, for my example (a material UI button) it was this:

foreign import button ::
  ReactComponent { onClick :: EventHandler, children :: Array JSX, color :: String, variant :: String }
'use strict'
exports.button = require("@material-ui/core/Button").default;

I'm also curious how I could make all the fields of the props somehow optional.

@megamaddu
Copy link
Member

You can use the same Union trick we used here: https://github.com/lumihq/purescript-react-basic/blob/master/src/React/Basic/DOM/Generated.purs#L119

This only works for FFI -- the type it creates can't really be implemented using PureScript. You can make some fields required by putting them in the Record type instead of the Union:

a
  :: forall attrs attrs_
   . Union attrs attrs_ Props_a
  => { someRequiredField :: Asdf | attrs }
  -> JSX

@alextes
Copy link

alextes commented May 24, 2020

Can this be reopened with the outcome of having an example? Or even multiple examples? There appears to be no information yet on how to deal with external components. Or perhaps as it appears there is no one true way, what I'm looking for is an overview of the options people have thought of. I'll share mine.

The following is all in the context of larger external components as often found in frameworks or libraries.

  1. ReactComponent with lots of Data.Nullable. This feels cumbersome to me at around five props up, where three or more props are passed null. It makes the types accurate and helpful, but it makes the JSX communicate a lot of nothing. Additionaly, as you need more props exposed from the external component you have to go back and update all the uses with nulls, or spend time up front to type props you don't yet care about.

  2. forall a. ReactComponent (Record a) obviously this is not the PureScript way but nevertheless I notice myself reaching for it as an escape hatch for components with many optional props that are all over the place. For example a button component. Yet, my frustration with the unsafe type brought me here 😄 .
    2a. I just thought of this one, I guess you could type the component as if it wants the full record, pass a record that is a subset of it and unsafeCoerce it. At least that way you get to keep the types as information.

type ButtonProps = { onClick :: Effect Unit, disabled :: Boolean }
foreign import button ::
	forall props props_. Union props props_ ButtonProps =>
	ReactComponent (Record props)

It appears easy to break. I broke it, by moving the union outside the foreign import. Spent time understanding FFI more deeply, fixing it. Similarly, this method appears to break down when some props are sets of props themselves.

  1. Use Data.Options. Designed for this scenario. I only tried it superficially myself. Creating the sets of props is a breeze, on the other hand, extra syntax in the middle of JSX, high boilerplate, what about props that are shared between many components? Like children?

@jvliwanag
Copy link
Contributor

I need to clean up my lib -- https://github.com/jvliwanag/purescript-oneof . But adding it as an option as well.

It allows for simple record declarations:

type Props = 
  { name :: UndefinedOr String
  , foo :: UndefinedOr (String |+| Number |+| Boolean)
  }

foreign import _myButton :: ReactComponent Props

myButton :: forall r. Coercible r Props => r -> JSX
myButton = element _myButton <<< coerce

sample = myButton { foo: "bar" } -- can omit name

@megamaddu
Copy link
Member

I wouldn't mind having an FFI example. You're correct that it's more because there's no one way to do it, and it's not really different from any other PureScript FFI.

  1. When I'm making a PS component which wraps a JS one and that JS component is otherwise hidden from the outside world, I reduce the Nullable noise by not defining props I won't use and making props I always use non-nullable, even if they're technically optional in the JS component. For example, if I'm wrapping a button component I will probably define onClick :: EventHandler and just always provide it, even if there are cases where it's a noop.
  • btw, you can define a "defaults" props record alongside your component, allowing you to call it like Button.button Button.defaults { onClick = do something } -- this allows you to add new props to the component without breaking existing usage, and this isn't specific to FFI components
  1. Yeah, once in a while I do this to hack something quickly, but options 1 or 3 are almost always better.

  2. This is great for simple components with lots of props, like the DOM components. It's useful when the FFI component is extremely flexible and you need to expose that in PS without lots of boilerplate. Like you said, though, it can have issues if any props have complicated types, like nested records. I would generally recommend the Data.Options approach.

  3. Yep, this is a good option

  4. One other option which is sometimes appropriate -- split the definition into two or more components. Sometimes certain props are required in combination. Defining the component multiple times with different props can simplify basic use cases while also making more complicated ones safer.

@alextes
Copy link

alextes commented May 27, 2020

Lots of good options, some new to me. Thanks!

@megamaddu megamaddu reopened this May 27, 2020
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

No branches or pull requests

6 participants