Solid supports templating in 3 forms JSX, Tagged Template Literals, and Solid's HyperScript variant. Although JSX is the predominate form. Why? JSX is a great DSL made for compilation. It has clear syntax, supports TypeScript, works with Babel, supports other tooling like Code Syntax Highlighting and Prettier. It was only pragmatic to use a tool that basically gives you that all for free. As a compiled solution it provides great DX. Why struggle with custom Syntax DSLs when you can use one so widely supported?
Still there is some confusion as to what JSX is and is not. JSX is an XML-like syntax extension to EcmaScript (https://facebook.github.io/jsx/). It is not a language or runtime. Those can be refered to as HyperScript. So while Solid's JSX and might resemble React it by no means works like React and there should be no illusions that a JSX library will just work with Solid. Afterall, there are no JSX libraries, as they all work without JSX, only HyperScript or React ones.
Rendering involves precompilation of JSX templates into optimized native js code. The JSX code constructs:
- Template DOM elements which are cloned on each instantiation
- A series of reference declarations using only firstChild and nextSibling
- Fine grained computations to update the created elements.
This approach both is more performant and produces less code then creating each element one by one with document.createElement.
More documentation is available at: babel-plugin-jsx-dom-expressions
Static attributes are created as part of the html template together. Expressions fixed and dynamic are applied afterwards in JSX binding order. While this is fine for most DOM elements there are some like input elements with type='range'
where order matters. Keep this in mind when binding elements.
Solid expects the UI to reflect its state. This means updating state on form actions. Failing to do so can cause unexpected behavior as setting state to the same value will not trigger an update even if the DOM value has diverged. In general it is recommended you handle forms in this "controlled" manner.
In some cases it might make sense to manage the form state outside of Solid via refs. These "uncontrolled" forms can also work. Just be conscious of the difference as mixing approaches can lead to unexpected results.
The easiest way to mount Solid is to import render from 'solid-js/dom'. render
takes a function as the first argument and the mounting container for the second and returns a disposal method. This render
automatically creates the reactive root and handles rendering into the mount container. Solid assumes full control of the mount container so use an element with no children.
import { render } from "solid-js/dom";
render(() => <App />, document.getElementById("main"));
on_____
handlers are event handlers expecting a function. The compiler will delegate events where possible (Events that can be composed and bubble) else it will fall back to Level 1 spec "on_____" events.
If you wish to bind a value to events pass an array handler instead and the second argument will be passed to your event handler as the first argument (the event will be second). This can improve performance in large lists when the event is delegated.
function handler(itemId, e) {/*...*/}
<ul>
<For each={state.list}>{item => <li onClick={[handler, item.id]} />}</For>
</ul>
This delegation solution works with Web Components and the Shadow DOM as well if the events are composed. That limits the list to custom events and most UA UI events like onClick, onKeyUp, onKeyDown, onDblClick, onInput, onMouseDown, onMouseUp, etc..
To allow for casing to work all custom events should follow the all lowercase convention of native events. If you want to use different event convention (or use Level 3 Events "addEventListener") use the "on" or "onCapture" binding.
<div on={{ "Weird-Event": e => alert(e.detail) }} />
While you could use a map function for loops they aren't optimized. While perhaps not as big of a deal in the VDOM since Solid is designed to not execute all the code from top down repeatedly we rely on techniques like isolated contexts and memoization. This is complicated and requires special methods.
<ul>
<For each={state.users} fallback={<div>No Users</div>}>
{user => (
<li>
<div>{user.firstName}</div>
<Show when={user.stars > 100}>
<div>Verified</div>
</Show>
</li>
)}
</For>
</ul>
Control flows can be imported from solid-js
but as a convenience the compiler will automatically import them.
Keyed list iteration:
<For each={state.list} fallback={<div>Loading...</div>}>
{item => <div>{item}</div>}
</For>
Optional second argument is an index signal:
<For each={state.list} fallback={<div>Loading...</div>}>
{(item, index) => <div>#{index()} {item}</div>}
</For>
Conditionally control content (make sure when
is boolean):
<Show when={state.count > 0} fallback={<div>Loading...</div>}>
<div>My Content</div>
</Show>
Or as a way of keying blocks:
<Show when={state.user} fallback={<div>Loading...</div>}>
{user => <div>{user.firstName}</div>}
</Show>
<Switch fallback={<div>Not Found</div>}>
<Match when={state.route === "home"}>
<Home />
</Match>
<Match when={state.route === "settings"}>
<Settings />
</Match>
</Switch>
<Suspense fallback={<div>Loading...</div>}>
<AsyncComponent />
</Suspense>
<SuspenseList revealOrder="forwards" tail="collapsed">
<ProfileDetails user={resource.user} />
<Suspense fallback={<h2>Loading posts...</h2>}>
<ProfileTimeline posts={resource.posts} />
</Suspense>
<Suspense fallback={<h2>Loading fun facts...</h2>}>
<ProfileTrivia trivia={resource.trivia} />
</Suspense>
</SuspenseList>
Note: You should never use this unless you know exactly what you are doing. Use
<For />
instead. Non-keyed or keyed by index is a dangerous anti-pattern that should never be used. It can mangle DOM state breaking(or incurring extra cost) in CSS animations/transisions, Rich DOM elements, Web Components, and DOM level plugins. With Solid you do not need an explicit key so there is no excuse. This exists just for integration with poorly designed benchmarks and libraries which default to this behavior.
Non-Keyed list iteration (rows keyed to index). Item is a signal:
<Index each={state.list} fallback={<div>Loading...</div>}>
{item => <div>{item()}</div>}
</Index>
Optional second argument is an index number:
<Index each={state.list} fallback={<div>Loading...</div>}>
{(item, index) => <div>#{index} {item()}</div>}
</Index>
Also available from solid-js/dom
:
<Portal mount={document.getElementById("modal")}>
<div>My Content</div>
</Portal>
Note these are designed to handle more complex scenarios like Component insertions. For simple dynamic expressions use boolean or ternary operator.
Refs come in 2 flavours. ref
which directly assigns the value, and which calls a callback (ref) => void
with the reference.
function MyComp() {
let myDiv;
setTimeout(() => console.log(myDiv.clientWidth));
return <div ref={myDiv} />;
}
On a native intrinsic element as the element executes the provided variable will be assigned. This form usually is used in combination with setTimeout
(same timing as React's useEffect
) or afterEffects
(same timing as React's useLayoutEffect
) to do work after the component has mounted. Like do a DOM measurement or attach DOM plugins etc...
When applied to a Component it acts similarly but also passes a prop in that is a function that is expected to be called with a ref to forward the ref (more on this in the next section):
function App() {
let myDiv;
setTimeout(() => console.log(myDiv.clientWidth));
return <MyComp ref={myDiv} />;
}
Callback form expects a function like React's callback refs. Original use case is like described above:
function MyComp(props) {
return <div ref={props.ref} />;
}
function App() {
let myDiv;
setTimeout(() => console.log(myDiv.clientWidth));
return <MyComp ref={myDiv} />;
}
You can also apply ref
on a Component:
function App() {
return <MyComp ref={ref => console.log(ref.clientWidth)} />;
}
This just passes the function through as props.ref
again and work similar to the example above except it would run synchronously during render. You can use this to chain as many ref
up a Component chain as you wish.
See solid-ssr