Skip to content

Files

Latest commit

6908c7b · Jun 1, 2020

History

History
230 lines (168 loc) · 9.1 KB

rendering.md

File metadata and controls

230 lines (168 loc) · 9.1 KB

Rendering

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.

JSX Compilation

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

Note on attribute binding order

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.

Note on forms

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.

Entry

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"));

Events

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) }} />

Control Flow

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.

For

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>

Show

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/Match

<Switch fallback={<div>Not Found</div>}>
  <Match when={state.route === "home"}>
    <Home />
  </Match>
  <Match when={state.route === "settings"}>
    <Settings />
  </Match>
</Switch>

Suspense

<Suspense fallback={<div>Loading...</div>}>
  <AsyncComponent />
</Suspense>

SuspenseList

<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>

Index

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

<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

Refs come in 2 flavours. ref which directly assigns the value, and which calls a callback (ref) => void with the reference.

ref

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.

Server Side Rendering (Experimental)

See solid-ssr