Skip to content

Overview

In Solid, a component is a function that runs once and returns DOM. Partas.Solid gives you two ways to write that function in F#:

  1. [<SolidComponent>] on a let binding.
  2. [<SolidTypeComponent>] on a member of a type. The type's properties become the component's props.

Both tell the Fable plugin to rewrite the body into JSX. Without one, the DSL compiles to nothing useful.

Inside element builders, match! turns F# patterns and guards into Solid Switch and Match arms. Supply a reactive expression to select the arm; changes to its pattern-bound payload update the content while preserving that arm's DOM.

SolidComponent

[<SolidComponent>] works on any let binding. Its parameters stay ordinary F# parameters.

[<StringEnum>]
type ButtonVariant =
    | Primary
    | Ghost

[<SolidComponent>]
let Button (title: string) (variant: ButtonVariant) =
    button (
        style =
            (match variant with
             | Primary -> "background: #2563eb; color: white; margin-right: .5em"
             | Ghost -> "background: transparent; margin-right: .5em")
    ) {
        title
    }

[<SolidComponent>]
let ButtonExample () =
    let variant, setVariant = createSignal Ghost

    div () {
        Button "Primary" Primary
        Button "Ghost" Ghost
        Button "Responsive" (variant ())
        button (onClick = fun _ -> setVariant (if variant () = Primary then Ghost else Primary)) {
            "Click me"
        }
    }
export function Button(title, variant) {
    return <button style={(variant === "ghost") ? "background: transparent; margin-right: .5em" : "background: #2563eb; color: white; margin-right: .5em"}>
        {title}
    </button>;
}

export function ButtonExample() {
    const patternInput = createSignal("ghost");
    const variant = patternInput[0];
    return <div>
        {untrack(Button.bind(undefined, "Primary", "primary"))}
        {untrack(Button.bind(undefined, "Ghost", "ghost"))}
        {untrack(Button.bind(undefined, "Responsive", variant()))}
        <button onClick={(_arg) => {
                patternInput[1]((variant() === "primary") ? "ghost" : "primary");
            }}>
            Click me
        </button>
    </div>;
}

In the JSX tab, a [<SolidComponent>] with parameters is called as a plain function, Button("Primary", "primary"), not used as a <Button /> tag. That is fine for small helpers. For a component other code will use, reach for [<SolidTypeComponent>]: it becomes a real tag with named props, for F# and JavaScript callers alike. See SolidComponent for the details.

SolidTypeComponent

This attribute defines a custom tag: a type whose properties are the props, with the body in a member marked [<SolidTypeComponent>].

[<Erase>]
type Greeting() =
    inherit div()

    [<Erase>]
    member val name: string = unbox null with get, set

    [<Erase>]
    member val loud: bool = unbox null with get, set

    [<SolidTypeComponent>]
    member props.View =
        props.name <- "world"

        div(style = (if props.loud then "font-weight: bold" else "")).spread props {
            $"Hello, {props.name}!"
        }

[<SolidComponent>]
let GreetingExample () =
    let loud, setLoud = createSignal false

    div () {
        Greeting()
        Greeting(name = "Partas", loud = loud ())
        button (onClick = fun _ -> setLoud (not (loud ()))) { "Toggle loud" }
    }
export function Greeting(props) {
    props = merge({
        name: "world",
    }, props);
    const PARTAS_OTHERS = omit(props, "loud", "name");
    return <div style={props.loud ? "font-weight: bold" : ""}
        {...PARTAS_OTHERS} n$={false}>
        {concat("Hello, ", props.name, "!")}
    </div>;
}

export function GreetingExample() {
    const patternInput = createSignal(false);
    const loud = patternInput[0];
    return <div>
        <Greeting />
        <Greeting name="Partas"
            loud={loud()} />
        <button onClick={(_arg) => {
                patternInput[1](!loud());
            }}>
            Toggle loud
        </button>
    </div>;
}
  • inherit div() gives Greeting every attribute a div has, as well as its own name and loud.
  • props.name <- "world" sets a default. The plugin turns assignments to props into props = merge({...}, props).
  • .spread props passes on every prop the body does not read. The plugin collects the reads into const PARTAS_OTHERS = omit(props, ...) and spreads that.
  • Prop reads stay as props.x in the JSX, so they stay reactive.
  • The member becomes a function named after the type. The member name, here View, does not matter.

Greeting is now used like any built-in tag. The JSX output page shows the exact shapes.

A type without a [<SolidTypeComponent>] member describes a component that already exists in JavaScript. Import it with [<Import>] and declare its properties:

namespace Partas.Solid.MyBindings

open Fable.Core
open Partas.Solid

[<Import("Tooltip", "some-js-library")>]
[<Erase>]
type Tooltip() =
    inherit div()

    [<Erase>]
    member val placement: string = unbox null with get, set
Tooltip(placement = "top", class' = "tip") { "More info" }

See SolidTypeComponent for the rules the member must follow, and tag interfaces for the interfaces a custom tag can implement instead of inheriting.

Edit this page