Zaid-Ajaj / Feliz

A fresh retake of the React API in Fable and a collection of high-quality components to build React applications in F#, optimized for happiness
https://zaid-ajaj.github.io/Feliz/
MIT License
544 stars 81 forks source link

Question about starting app with React.functionComponent #134

Closed JordanMarr closed 4 years ago

JordanMarr commented 4 years ago

I just found Feliz yesterday, and it has me very excited! I love the API, I like that the React Hooks function signatures look closer to the originals, and the documentation is some of the best I've seen!

I've been doing projects with F# back-end and React/TypeScript front-end for awhile now. I love React functional components + Hooks API. The only reason I have hesitated on using Fable is because I think the Elmish pattern would be too many degrees of change for my C# teammates. I wasn't even aware that you could use Fable without Elmish until earlier this week because all the demos I had seen previously focused on Elmish. IMO, it looks overly complex when multiple pages are introduced to Elmish (too much boilerplate code). That's why Feliz - with its emphasis on React.functionComponents and the React.elmishComponent - looks so amazing to me!

Now onto my question: Currently your Feliz template Main.fs calls mkProgram with the Elmish App.fs. I would like to create a sample app to show my team where App.fs consists of a React.functionComponent instead (because I think that a strongly typed React would be an easier sales pitch than F# plus Elmish).

So then what would be the best way to bootstrap a Function component in this way? I'm assuming that I wouldn't use mkProgram, since that's for Elmish (although I suppose I could if I just passed in stubs for the init and update functions). I think I saw a React.render somewhere.. would I use that instead?

Finally, would there be any drawbacks to this approach? (for example, I see a lot of Program.with... functions that I would not be able to use). I like the idea of being about start an app using React.functionComponents and then be able to choose to branch out to use React.elmishComponents only when I want.

Shmew commented 4 years ago

You can do something like this:

module App

open Feliz

let counter = React.functionComponent(fun () ->
    let (count, setCount) = React.useState(0)
    Html.div [
        Html.h1 count
        Html.button [
            prop.text "Increment"
            prop.onClick (fun _ -> setCount(count + 1))
        ]
    ])

open Browser.Dom

ReactDOM.render(counter, document.getElementById "root")

You could build an entire application without Elmish, probably using something like context where needed.

I think you'll find that once the application grows to a certain side you'll need something like Elmish to help you manage your state. Similar to how in the React world many people start without Redux, but in the end any sufficiently sized application is going to need the functionality that a library like that provides.

Zaid-Ajaj commented 4 years ago

Hello Jordan,

I am happy to hear that you are enjoying Feliz so far :heart:

As for your question, I think @Shmew answered it best. Using Feliz, you should be able to write a standalone React application without Elmish. The reason I am using Elmish in the template is because I think the library still is pretty awesome for state management but it is really up to you in how you want to mix and match the approaches with state management.

I believe that Elmish falls short when you have to encapsulate every little detail (for example whether an accordion is open/closed) in the "global" state which is where React components come into play by making small stateful components without polluting the global state that is managed by Elmish.

These "small stateful components" can even be entire pages written with React.elmishComponent but that too has it's drawbacks, for example in how the component decides to re-initialize it's state when the input props change. Currently is it using reference equality (causes a little problem with HMR when you only change some Html and the component decides to re-initialize it's state again) but maybe I should delegate that choice to the user so there are still some stuff I need to figure out with this library and what it's limitations are.

JordanMarr commented 4 years ago

Thank you both for the guidance.

What would be the difference between using a React.elmishComponent vs a React.functionComponent + React.useReducer?

Zaid-Ajaj commented 4 years ago

React.useReducer does not use/run side-effects and requires the reduce function "update" to be of type State' -> Msg' -> State' where as React.elmishComponent takes an update function of the signature State' -> Msg' -> State' * Cmd<Msg'> and executes the commands/side-effects once returned by the update function or but init.

I tried to write React.elmishComponent in terms of a variant of React.useReducer + custom hooks but couldn't do it at that time: there were a couple of problems with scheduling command execution where commands were executed too many times or weren't executed at all, so I went and wrote an old style React component with a class and internal state to build React.elmishComponent

JordanMarr commented 4 years ago

I can see how it does make sense to store Elmish global state in App and then use the Feliz.Router to send any global state and update functions to the various function component pages like your hybrid example. This seems like an ideal approach, and your hybrid example really brings it to life.

I'm now in the process of porting over one of my current React projects. This will be my first Fable project! So excited!

As an aside, I can imagine this hybrid elmish/stateful component approach being equally useful in the other Elmish frameworks.