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

Designing the React Helper API #70

Closed
alfonsogarciacaro opened this issue Mar 19, 2016 · 3 comments
Closed

Designing the React Helper API #70

alfonsogarciacaro opened this issue Mar 19, 2016 · 3 comments

Comments

@alfonsogarciacaro
Copy link
Member

I'm currently designing an API to make it easier to interact with React from F# and the language combination is providing several interesting opportunities for static checking. However, as expected there are some rough ends and I have doubts about how to define the properties in a React DOM element. Currently we have:

Option 1: Dynamic properties

module R = Fable.ReactHelper
R.input [
    "type" ==> "text"
    "placeholder" ==> "Your name"
    "value" ==> x.state.author
    "onChange" ==> x.handleAuthorChange
] []

This is the most flexible option as we can pass any property in a plain JS object. Thanks to inlining functions we don't need to use createObj here and we also save some braces. If you think the ==> is too verbose, shortening is also trivial: let inline (=>) a b = a ==> b

The problem is that we don't have any static checking or auto completion for the properties. So I'm considering using a lambda instead. But now we have to decide where to get the signature from. At first I took the one from the IntrinsicElements defined in the React definition file:

Option 2: Mutating HTMLElement properties

R.input (fun p ->
    p.``type`` <- "text"
    p.placeholder <- "Your name"
    p.value <- unbox x.state.author
    //p.onchange <- unbox x.handleAuthorChange
    p?onChange <- x.handleAuthorChange
) []           

Now we have static checking and auto completion for the properties which is very nice. Note however that some frictions with the type definitions start to show up and need to resolved with unbox. The biggest problem with this approach is that, though the TypeScript definition uses the standard DOM elements, some attributes have a different spelling in React. In the example above, setting onchange won't work, so we have to set onChange dynamically. This almost breaks all the benefits of static checking :(

Our next option then is to use React.HTMLAttributes instead to be sure we're using the correct spelling:

Option 3: Mutating React.HTMLAttributes properties

R.input (fun p ->
    p.``type`` <- Some "text"
    p.placeholder <- Some "Your name"
    p.value <- unbox x.state.author
    p.onChange <- unbox x.handleAuthorChange
) []

Ok, all the properties have the spelling as React expects it but we now face other problems: first the frictions with the signature multiply because all properties are defined as optional and other Typescript custom elements are used like erased union types or custom-defined functions; and second, in the previous option we had the specific attributes for input tags but here every HTML element share the same attributes. However, this may still be the best option.

There'd be a fourth option but I'm not considering it very much:

Option 4: Using a custom HTMLAttributes record

R.input (fun p ->
    { p with
        ``type`` = Some "text"
        placeholder = Some "Your name"
        value = unbox x.state.author
        onChange = unbox x.handleAuthorChange
}) []

This one is likely the most idiomatic in F# but the indentation rules get more complicated ({ p with cannot be put in the first line) and we don't have auto completion here to discover the properties of p, as far as I know. Most importantly, adopting this style would require creating a custom record for HTMLAttributes as the ones from TypeScript are parsed as interfaces (there are good reasons for that), so this would add an extra step in the maintenance of the Fable.Import.React file.

Here are the options to make the decision and I would love to hear your say on this. I know it's possible to add several options to the API, but this would probably make it more confusing for newcomers and I'd prefer to make it simple at first. Thanks a lot for your help in advance!

@Krzysztof-Cieslak
Copy link
Member

Option 5.
Extend option 1. to add typed attributes. I was doing similar API for FunScript back in the days.
https://github.com/FractalProject/Fractal.Sample/blob/master/client/App.fsx#L65-L74
https://github.com/FractalProject/Fractal/blob/master/src/Fractal.DOM.fs

Option 6.
Go super crazy and create computation expression with custom keywords.

@davidtme
Copy link
Contributor

For newcomers I think Option 4 looks to be the best, also no magic strings and it keeps thing immutable.

@alfonsogarciacaro
Copy link
Member Author

Thanks for the suggestion, @Krzysztof-Cieslak! At first I was afraid that this would require more maintenance work but after some consideration I realised this was the best option. I built on your code and wrote a script to generate (partially) the helper module:
https://github.com/fsprojects/Fable/blob/master/samples/browser/react/public/Fable.ReactHelper.fs

The render code is much prettier now with full static checking :)
https://github.com/fsprojects/Fable/blob/master/samples/browser/react/public/components.fs#L104-L124

There's still work to on the React Helper, but I'm closing the issue for now. Thank you all for your help!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants