Skip to content

Coming from React or Solid

The Signy version of what you already know.

Signy has fine grained reactivity like Solid and class components like older React. Two ideas carry over from Solid and surprise React users: render() runs once, and only functions bound in the template update. See How Signy works.

From React

ReactSigny
function App() { ... }class App extends Component { render() { ... } }, and render() runs once
const [count, setCount] = useState(0)count = new Signal(0), then this.count.value and this.count.value = 1
setCount((c) => c + 1)this.count.set((c) => c + 1)
{count} in JSX${() => this.count.value} in html, or {this.count.value} in compiled JSX
useMemo(() => a * b, [a, b])new Computed(() => this.a.value * this.b.value), no dependency list
useEffect(fn, [dep])effect(fn) in setup(), it tracks what it reads
useEffect(() => ..., [])this.onMount(() => ...)
The cleanup returned from useEffectonCleanup() in an effect, or the function returned from onMount
useRef() for an elementcreateRef() and ref=${this.input}
useRef() for a valueA plain class field
createContext, useContextcreateContext(), Ctx.provide(this, value), Ctx.inject(this)
{ok && <Panel />}Show({ when: () => ok.value, children: ... })
items.map((item) => <Row key={item.id} />)For({ each: () => items.value, keyBy: 'id', children: ... })
React.lazy, <Suspense>lazy(), Suspense
createPortalPortal
Error boundary classErrorBoundary({ fallback, children })
useTransitionuseTransition(), which batches writes into the next microtask and does not interrupt rendering, see Transitions
className, htmlForclass, for
ReactDOM.createRoot(el).render(<App />)application(App).mount(el)

Habits to drop

  • Re-rendering. Nothing re-renders a component. A value read outside a function binding stays as it was at render time.
  • Dependency arrays. Effects and computeds track what they read. A signal read in an effect is a dependency, so read the ones you do not want with untrack().
  • Controlled inputs. value=${() => ...} sets the attribute. Read input in onInput and reset the field through a ref, see Refs and forms.
  • Props that change. Props are read once. Pass a function and call it in a binding, see Props that change.
  • Synchronous updates. The DOM updates in a microtask after a write. In tests, call flushAll().

From Solid

SolidSigny
const [count, setCount] = createSignal(0)const count = signal(0), read count.value, write count.value = 1
createMemo(fn)computed(fn)
createEffect(fn)effect(fn), first run in a microtask
on(dep, fn)on(dep, fn), but fn is tracked too, see on()
createRoot, onCleanup, untrack, batchThe same names
onMountthis.onMount() on a component
createSelectorcreateSelector, which returns a signal: isSelected(id).value
createStore, produce, unwrapstore() with .state and .set(), produce, unwrap
createResourceresource(), which does not suspend
Show, For, Index, Switch, Match, Portal, ErrorBoundaryThe same, callable as functions or used as tags
lazy, SuspenseThe same
createContext, useContextcreateContext(), then provide(this, value) and inject(this) on a component
Function componentsClass components, with signals as fields
Solid RouterThe built in router
JSX through babel-preset-solidThe html template with no build, or JSX through babel-plugin-jsx-dom-expressions

Differences to watch

  • Accessors. A signal is an object with .value, not a getter function. Pass () => count.value where Solid takes count.
  • Timing. An effect() first runs in a microtask, after mount() has returned. Template bindings render synchronously, as in Solid.
  • Resources and Suspense. A resource never triggers Suspense. Only lazy() does.
  • Store writes. Assigning to store.state works and notifies. Solid stores are read only outside the setter.