Closed alfonsogarciacaro closed 5 years ago
Hmm, this may be another case when I get carried away by abusing Emit
. At first I thought the rules of hooks would prevent normal function composition, but this seems to work too:
let memoWithState<'P,'S> displayName (initState: 'S) (f: 'P->'S->('S->unit)->ReactElement) =
memoBuilder displayName <| fun (props: 'P) ->
let state, setState = Hooks.useState(initState)
f props state setState
// Usage
type Props = { MessageFormat: string }
let MyComponent =
memoWithState "MyComponent" "" <| fun (props: Props) state setState ->
div [ Class "main-container" ]
[ input [ Class "input"
Value state
OnChange (fun ev -> ev.target?value |> setState) ]
span [] [ str (String.Format(props.MessageFormat, state)) ] ]
MyComponent { MessageFormat = "Hello, {0}!" }
|> mountById "app"
So we may provide the close-to-the-metal bindings and then recommend how to combine them and/or provide helpers for the most common helpers (like memoWithState).
Those rules of hooks are tricky. They says hooks shouldn't be called from a nested function but this seems to work.
let MyComponent =
memoWithState "MyCom" "" <| fun (props: Props) state setState ->
// This is already happening in a nested call
Hooks.useEffect(fun () ->
printfn "New render: %s" state
None)
div [ Class "main-container" ]
[ input [ Class "input"
Value state
OnChange (fun ev -> ev.target?value |> setState) ]
span [] [ str (String.Format(props.MessageFormat, state)) ] ]
According to the detailed explanation, apparently the problems arise when the hooks are not called consistently in each render, but that's not the case here. I guess that's why it's working fine 🤷♂️
I am always to provide an API as close as possible to the native implementation. And then if needed, provide helpers to make it easier for people to use in an F# friendly way.
This allows people to just use what works via the friendly helpers while allowing them to explore other solution or specific needs they have by using the native API.
About the usage of the hooks, I don't have much idea because I don't yet understand they fully and I am still learning the memo
feature 😂.
One thing for sure is that I don't like using <|
operators. I know that dsyme explains all the time that we should avoid them. So we should provide an API that's clean without <|
Not sure if I'm on board with the Hooks type as Fable.Helpers thing.
I would stick closer to the original API.
import { useState } from 'react';
function Example() {
// Declare a new state variable, which we'll call "count"
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
Should that not be
open Fable.React
let Example () =
let [count, setCount] = useState(0);
div [ ]
[ p [ ]
[ sprintf "You clicked %d times" count |> str ]
button [ OnClick (fun _ -> setCount(count + 1))
[ str "setCount(count + 1)}>
Click me" ] ]
@nojaf Unfortunately the syntax let [count, setCount] = useState(0)
is not possible:
ParamArray
, et. So their capacity to emulate JS functions is limited. The state hook doesn't have an optional argument, but the effect hook does.let [|count; setCount|] = ...
(note you also need a semicolon instead of a comma which is a source of confusion as it has happened here). And there's also a big problem with this, you cannot type an array of specific length and with different types in F# so the signature of useState would become 'T -> obj array
, forcing users to make an unsafe cast of the return values. You will also get a warning from the F# compiler.Given that Fable compiles tuples as JS arrays, this is a great opportunity to use a tuple to type the return value of useState
.
Ok, in my sample I used a list but I meant a tuple. I would use Option type to represent optional argument instead of optional arguments but that is personal preference.
Given that Fable compiles tuples as JS arrays, this is a great opportunity to use a tuple to type the return value of
useState
.
Yes, a tuple for the return value of useState
would be great. :)
Hi guys, any decision/progress on this?
An alpha version of Fable.React 5 with basic support for React hooks is already pushed: https://github.com/fable-compiler/fable-react/blob/dd4473088012ec6875420e4ea67f546c113bb855/src/Fable.React.Helpers.fs#L42-L49
However, this release depends on updates of several other packages and I need to make some testing and write a post explaining about the updates. Hopefully it'll happen this week. If you can't wait, as the binding is just a few lines, you can use directly in your project :)
I'm happily using react hooks with Fable.React 5. However, useReducer
seems to be missing. What's the reason for this?
Probably an omission as it's looks similar to what Elmish does for us.
We would accept a PR for adding it :)
I use this in my own projects:
type ReduceFn<'state,'msg> = ('state -> 'msg -> 'state)
type Dispatch<'msg> ='msg -> unit
let useReducer<'state,'msg> (reducer: ReduceFn<'state,'msg>) (initialState:'state) : ('state * Dispatch<'msg>) = import "useReducer" "react"
Seems React Hooks will be shipping in stable form soon and the API doesn't seem to have changed much. We will have to add bindings for them soon and as usual we've two alternatives: a) match the native API as close as possible, or b) be a bit imaginative and try to make something more F# friendly. I've been playing with the two.
Matching native API
REPL Example
Let's be imaginative
For example to encourage creation of
memo
stateful components:REPL Example
What do you think? As always, having more opinionated helpers is a bit risky because we need extra documentation, we may not cover all the cases, etc. Also, it seems that there'll be more hooks coming and it's also possible to create custom hooks by combining them, so we may need to stick to the official API anyways. Something that concerns me in this case is how to enforce/recommend the rules of hooks.
cc @vbfox @MangelMaxime @Zaid-Ajaj