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
| React | Signy |
|---|---|
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 useEffect | onCleanup() in an effect, or the function returned from onMount |
useRef() for an element | createRef() and ref=${this.input} |
useRef() for a value | A plain class field |
createContext, useContext | createContext(), 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 |
createPortal | Portal |
| Error boundary class | ErrorBoundary({ fallback, children }) |
useTransition | useTransition(), which batches writes into the next microtask and does not interrupt rendering, see Transitions |
className, htmlFor | class, 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 inonInputand 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
| Solid | Signy |
|---|---|
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, batch | The same names |
onMount | this.onMount() on a component |
createSelector | createSelector, which returns a signal: isSelected(id).value |
createStore, produce, unwrap | store() with .state and .set(), produce, unwrap |
createResource | resource(), which does not suspend |
Show, For, Index, Switch, Match, Portal, ErrorBoundary | The same, callable as functions or used as tags |
lazy, Suspense | The same |
createContext, useContext | createContext(), then provide(this, value) and inject(this) on a component |
| Function components | Class components, with signals as fields |
| Solid Router | The built in router |
JSX through babel-preset-solid | The 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.valuewhere Solid takescount. - Timing. An
effect()first runs in a microtask, aftermount()has returned. Template bindings render synchronously, as in Solid. - Resources and Suspense. A resource never triggers
Suspense. Onlylazy()does. - Store writes. Assigning to
store.stateworks and notifies. Solid stores are read only outside the setter.