Experimental Features
Partas.Solid.Experimental holds syntax sugar that has seen less real-world use than the rest of the library.
Computation expressions
Many solid-js functions take a lambda with no parameters, such as onSettled or children. These builders write the lambda and the call for you. They take no options: for an option or another overload, call the function directly.
open Partas.Solid.Experimental
memo derives a value, effect reacts to it, and mount runs once after the first render. The JSX tab shows the calls they become:
[<SolidComponent>]
let BuilderCounter () =
let count, setCount = createSignal 1
let lastSeen, setLastSeen = createSignal 0
let settled, setSettled = createSignal false
let doubled = memo { count () * 2 }
effect {
let! d = doubled
setLastSeen d
}
mount { setSettled true }
div () {
button (onClick = fun _ -> setCount (count () + 1)) { "Add one" }
p () { $"doubled = {doubled ()}, the effect last saw {lastSeen ()}, settled = {settled ()}" }
}
export function BuilderCounter() {
const patternInput = createSignal(1);
const count = patternInput[0];
const patternInput_1 = createSignal(0);
const patternInput_2 = createSignal(false);
const doubled = createMemo((_arg) => ((count() * 2) | 0));
createEffect((_arg_2) => (doubled() | 0), (_arg_1) => {
patternInput_1[1](_arg_1);
});
onSettled(() => {
patternInput_2[1](true);
});
return <div>
<button onClick={(_arg_3) => {
patternInput[1](count() + 1);
}}>
Add one
</button>
<p>
{`doubled = ${doubled()}, the effect last saw ${patternInput_1[0]()}, settled = ${patternInput_2[0]()}`}
</p>
</div>;
}
| Builder | Same as |
|---|---|
lambda { e } | fun () -> e |
memo { e } | createMemo (fun _ -> e) |
mount { e } | onSettled (fun () -> e) |
cleanup { e } | onCleanup (fun () -> e) |
children { e } | children (fun () -> e) |
lazyload { e } | lazy' (fun () -> e), with importDynamic |
reaction { e } | createReaction (fun () -> e), returning its track function |
effect
effect is Solid 2's two-phase effect. The let! names the tracked source and becomes the compute function; the rest of the body is the effect function, and gets the value.
The
let!is required, and there can only be one. It binds an accessor (unit -> 'T).To track several sources, bind a
lambdathat reads them all:effect { let! first, last = lambda { firstName (), lastName () } console.log $"{first} {last}" }Statements before the
let!run once, when the builder runs. They are not part of the effect.The effect body is untracked: signals it reads do not re-run it.
A 2.x effect { } without a let! no longer compiles. Move the reads that should trigger it into a let!.
memo
The last expression is the value. The builder does not give you the previous value; call createMemo directly when you need it. A cleanup inside a memo, or inside the lambda of an effect's let!, runs before each recomputation and when the owner is disposed.
reaction
reaction returns a track function. Call it with the source to watch: the body runs once, the next time that source changes. Call track again to watch for the next change. Here the second toggle changes nothing:
[<SolidComponent>]
let FirstOpen () =
let isOpen, setOpen = createSignal false
let note, setNote = createSignal "Not opened yet"
let track = reaction { setNote "Opened for the first time" }
track (fun () -> box (isOpen ()))
div () {
button (onClick = fun _ -> setOpen (not (isOpen ()))) { "Toggle" }
p () { $"Open: {isOpen ()}. {note ()}" }
}
export function FirstOpen() {
const patternInput = createSignal(false);
const isOpen = patternInput[0];
const patternInput_1 = createSignal("Not opened yet");
createReaction(() => {
patternInput_1[1]("Opened for the first time");
})(isOpen);
return <div>
<button onClick={(_arg) => {
patternInput[1](!isOpen());
}}>
Toggle
</button>
<p>
{`Open: ${isOpen()}. ${patternInput_1[0]()}`}
</p>
</div>;
}
An if ... then without an else, followed by another statement, does not type-check inside a reaction block.
children
let resolved = children { props.children }
let hasChildren = lambda { resolved.toArray().Length > 0 }
Removed builders
batch { } and selector { } went with batch and createSelector, which Solid 2 dropped. Use flush (fun () -> ...) and createProjection.
Erased union implicit casting
Bindings often type an argument as an erased union such as U2<string, int>, and Fable's unions need !^ to cast a value into it. The U module in Partas.Solid.Experimental redefines U2 to U9 with implicit conversions, so a plain value converts without !^. Partas.Solid's HTML, SVG and style bindings are typed with it.
open Fable.Core
open Partas.Solid.Experimental.U
[<Import("somefunction", "somelibrary")>]
let numberOrString (value: U2<string, int>) : obj = jsNative
numberOrString "Something"
numberOrString 2
numberOrString 3. // error: float is not in the union
F# may warn about each implicit conversion; !^ silences it. The conversion does not apply to array or list literals: give the literal its type, or use !^:
let numberOrList (value: U2<int, int list>) : obj = jsNative
numberOrList [ 5; 6 ] // error: expected U2<int, int list>, got 'a list
numberOrList ([ 5; 6 ]: int list) // works
numberOrList !^[ 5; 6 ] // works
