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#:
[<SolidComponent>]on aletbinding.[<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()givesGreetingevery attribute adivhas, as well as its ownnameandloud.props.name <- "world"sets a default. The plugin turns assignments to props intoprops = merge({...}, props)..spread propspasses on every prop the body does not read. The plugin collects the reads intoconst PARTAS_OTHERS = omit(props, ...)and spreads that.- Prop reads stay as
props.xin 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.
