Feliz

repository·main·Indexed 20 days ago

https://github.com/fable-hub/feliz

A type-safe, optimized React DSL for F# and Fable. Feliz provides discoverable attributes, full React hook support, and seamless integration with the Elmish architecture. It includes a type-safe CSS implementation and ecosystem components such as Feliz.PigeonMaps for interactive maps and Feliz.Recharts for type-safe charting based on the Recharts library.

Tokens
59.9K
Snippets
185
Records
219
Agent score
67%

What's inside Feliz

  1. What is Feliz?

    main

    Feliz is a React DSL (Domain Specific Language) for F# designed to build React applications with a focus on developer experience and type safety. It provides a fresh, optimized way to write React components using F# syntax while maintaining full compatibility with the React API (hooks, context, etc.).

    module App
    
    open Feliz
    
    [<ReactComponent>]
    let Counter() =
        let (count, setCount) = React.useState(0)
        Html.div [
            Html.button [
                prop.style [ style.marginRight 5 ]
                prop.onClick (fun _ -> setCount(count + 1))
                prop.text "Increment"
            ]
    
            Html.button [
                prop.style [ style.marginLeft 5 ]
                prop.onClick (fun _ -> setCount(count - 1))
                prop.text "Decrement"
            ]
    
            Html.h1 count
        ]
    
    open Browser.Dom
    
    let root = ReactDOM.createRoot (document.getElementById "root")
    root.render (Counter())
  2. Use Femto to synchronize F# and JavaScript dependencies

    main

    Femto is a command-line tool designed for Fable projects. It scans your project to detect required npm packages and automatically installs or updates them. This ensures that your F#/.NET and JavaScript dependencies remain consistent.

    Femto supports the following package managers:

    • npm
    • yarn
    • pnpm
    • poetry
  3. Use Feliz.Recharts for type-safe charting

    main

    Feliz.Recharts provides Feliz-style bindings for the recharts library. It offers a one-to-one translation of the original Recharts API but makes it type-safe and easily discoverable within F# using the Feliz pattern. You can compose charts using components like Recharts.areaChart, Recharts.xAxis, Recharts.yAxis, and Recharts.area.

    open Feliz
    open Feliz.Recharts
    
    // Example usage of an Area Chart
    [<ReactComponent>]
    let SampleChart() =
        Recharts.areaChart [
            areaChart.width 730
            areaChart.height 250
            areaChart.data data
            areaChart.children [
                Recharts.xAxis [ xAxis.dataKey (fun point -> point.name) ]
                Recharts.yAxis [ ]
                Recharts.area [
                    area.monotone
                    area.dataKey (fun point -> point.uv)
                    area.stroke "#8884d8"
                    area.fill "url(#colorUv)"
                ]
            ]
        ]
  4. What is Fable?

    main
    Fable is an F# to JavaScript/TypeScript compiler designed for F# web development. It allows developers to write client-side code using F#'s powerful type system and functional programming features while maintaining full interoperability with the JavaScript ecosystem. This enables the creation of robust and maintainable web applications using F#.
  5. Specify types for React context in Feliz

    main

    When using React.createContext in Feliz, you must provide a type annotation for the context value. This is necessary because F#'s strong typing requires knowing the shape of the data being shared, whereas JavaScript's useContext is dynamically typed.

    For example, if your context holds an optional tuple of an integer and a setter function, you must annotate it as (int * (int -> unit)) option.

    let CounterContext = React.createContext(None: (int * (int -> unit)) option)
  6. How to handle React component props in F# bindings

    main

    When writing bindings for React components, you map JavaScript props to F# function arguments.

    1. Required Props: Define them as standard arguments (e.g., context: obj).
    2. Optional Props: Use the F# optional parameter syntax ?propName: Type (e.g., ?modal: bool).
    3. Children: Always include children: ReactElement as an argument to allow the component to wrap other elements, mimicking JSX syntax.
    4. Transpilation: The [<ReactComponent>] attribute handles the conversion from your F# function arguments into the single props object expected by the underlying JavaScript component.
    // Example of a binding with required, optional, and children props
    [<ReactComponent("ComponentName", "package-name")>]
    static member ComponentName 
        (requiredProp: obj, 
         children: ReactElement, 
         ?optionalProp: bool) = 
        React.Imported ()
  7. Spreading props using `yield!`

    main

    To mimic the JSX spread operator (...props), you can use the yield! syntax inside a property list. This allows a component to accept an arbitrary list of IReactProperty and apply them to the underlying element.

    Precedence Rules:

    • Standard Props: The last occurrence of a property in the list takes precedence. If you yield! props after defining specific properties, the spread props can overwrite them.
    • Children Prop: The children property is an exception; the first occurrence takes precedence.
    [<ReactComponent(true)>]
    let MyButton(children: ReactElement, props: IReactProperty list) =
        Html.button [
            yield! props // same as `...props` in JSX. Can overwrite existing props
        ]
  8. Handle JavaScript Promises in Fable

    main

    JavaScript uses Promises for asynchronous operations, while F# uses async. To work effectively with JS libraries, it is highly recommended to use the Fable.Promise NuGet package.

    Key distinction: promise is eager and starts executing as soon as it is created, whereas F# async is lazy and requires an explicit start (e.g., Async.Start).

    To bridge the two worlds, use:

    • Async.AwaitPromise to convert a Promise to an F# async computation.
    • Async.StartAsPromise to convert an F# async computation to a Promise.

    If you have a function returning a Promise<unit> that you need to use in a context like a React onClick handler, use fetchData(...) |> Promise.start to execute it.

    // Example of starting a promise in a handler
    let onClick = fun _ -> fetchData() |> Promise.start
  9. Use Fable interop attributes for JS objects and components

    main

    When creating bindings for JavaScript libraries in F#, use these attributes to control how F# code is transpiled to JS:

    • [<ReactComponent("Name", "package")>]: Imports a React component from a specific package.
    • [<ReactComponentAttribute(true)>]: Marks a function as the default export for a module.
    • [<ParamObject; Emit("$0")>]: Used on a class constructor to build a plain JavaScript object (POJO) from named/optional arguments and emit it directly.
    • [<Erase; Mangle(false)>]: Used on modules or types to prevent F# name mangling and to treat Discriminated Unions as plain JS objects (useful for complex JS shapes). Use with caution: erased unions only work if cases have distinct shapes.
    • [<Emit("js_code")>]: Tells Fable to emit the exact JS code provided, using $0, $1, etc., for parameter references.
  10. Define type-safe bindings using Interfaces with abstract members

    main

    Instead of using dynamic access (?), you can define the shape of a JavaScript library using F# interfaces with abstract members. This provides type safety without increasing your bundle size, as Fable does not generate any JavaScript code for these interfaces.

    Use obj for complex configuration objects where you don't want to define the full schema, and use the ? suffix in the parameter list to make them optional.

    type IDataFrame = 
        interface end
    
    type IDFD =
        abstract member readCSV: string * ?options: obj -> JS.Promise<IDataFrame>
        abstract member toCSV: IDataFrame * ?options: obj -> JS.Promise<unit>
  11. How the Elmish Program abstraction works

    main

    The Program<'arg, 'model, 'msg, 'view> type is the central abstraction of Elmish. It encapsulates the entire lifecycle of an application:

    1. Initialization: init takes an argument and produces the starting model and an initial Cmd.
    2. Update Loop: When a msg is dispatched, the update function takes the current model and the msg, returning a new model and a potential Cmd.
    3. Subscriptions: The subscribe function looks at the current model and returns a set of active subscriptions. The runtime manages starting and stopping these automatically.
    4. View: The view function renders the model and provides a Dispatch<'msg> function so the UI can send messages back to the loop.
    5. Error Handling: The onError function catches exceptions during the update loop or command execution.
    6. Termination: The loop checks a predicate on every message to see if the program should shut down.