Skip to main content

Solid 2 integration

The /solid adapter targets Solid 2.0.0-rc.18 only. Solid 2 is a release candidate, so pin this exact version. Solid 1 is no longer supported by this adapter. The import and return shape stay the same:

import {useClassyStore} from '@codebelt/classy-store/solid';

// Inside a component or live createRoot():
const state = useClassyStore(store); // () => Snapshot<typeof store>

Install the runtime packages for a browser application:

npm install @codebelt/classy-store solid-js@2.0.0-rc.18 @solidjs/web@2.0.0-rc.18

The repository example uses @solidjs/vite-plugin@3.0.0-next.51, @solidjs/compiler@2.0.0-rc.18, and Vite 8.3.5. The Vite plugin requires Node.js 22.12 or later. Use @solidjs/web for DOM imports and jsxImportSource: "@solidjs/web" in TypeScript configuration. Replace the Solid 1 vite-plugin-solid package with @solidjs/vite-plugin.

Inputs, computed values, and lists​

Read immutable snapshots through the returned accessor in JSX or createMemo. Write to the original store through actions in event handlers. The adapter publishes whole snapshots; it does not expose React's selector or auto-tracking options.

import {createClassyStore} from '@codebelt/classy-store';
import {useClassyStore} from '@codebelt/classy-store/solid';
import {For} from '@solidjs/web';
import {createMemo, flush} from 'solid-js';

class FormStore {
text = 'Hello';
items = [{id: 1, label: 'First'}];

get length() {
return this.text.length;
}
setText(text: string) {
this.text = text;
}
replaceItems() {
this.items = [{id: 1, label: 'Updated'}, {id: 2, label: 'Second'}];
}
}

// Shared state for this client-rendered example.
const formStore = createClassyStore(new FormStore());

export function Form() {
const state = useClassyStore(formStore);
const uppercase = createMemo(() => state().text.toUpperCase());

function handleReset() {
formStore.setText('Hello');
flush();
// Reactive reads and DOM bindings now reflect the mutation.
console.log(uppercase());
}

return (
<section>
<label>
Text
<input
value={state().text}
onInput={(event) => formStore.setText(event.currentTarget.value)}
/>
</label>
<p>{uppercase()} ({state().length} characters)</p>
<button type="button" onClick={handleReset}>Reset</button>
<button type="button" onClick={() => formStore.replaceItems()}>
Replace items
</button>
<ul>
<For each={state().items} keyed={(item) => item.id}>
{(item) => <li>{item().label}</li>}
</For>
</ul>
</section>
);
}

Solid 2's custom-key For callback receives an item accessor. A stable key preserves the row's DOM when an immutable snapshot replaces the item object; read item().label in JSX so its content updates too.

Timing​

The adapter forwards each store mutation to its internal Solid signal synchronously. Solid 2 then schedules its normal reactive updates, coalescing consecutive writes. The adapter never calls flush() itself.

Use Solid's flush() after an action when imperative code needs updated computed values or DOM immediately. Ordinary event handlers can let Solid apply the updates normally. Avoid capturing snapshot fields at component setup time; read them in JSX or a memo to retain reactivity.

Ordinary subscribe(store, callback) listeners remain microtask-batched. Calling Solid's flush() does not flush these core listeners. Read snapshot(store) when imperative code needs the current core state independently of Solid's scheduler.

Ownership and cleanup​

Call useClassyStore() during component setup or inside a live createRoot(). Calls without an owner, or with an already-disposed owner, throw before a subscription is created. Unmounting the component or disposing its owner removes the subscription. Nested roots follow Solid 2's parent-owned lifetime; independent roots clean up independently.

For manual ownership, retain and call the root's disposer:

import {createRoot, flush} from 'solid-js';
import {useClassyStore} from '@codebelt/classy-store/solid';

const session = createRoot((dispose) => ({
state: useClassyStore(formStore),
dispose,
}));

formStore.setText('Updated');
flush();
console.log(session.state().text);
session.dispose();

This integration pass covers client rendering, real inputs, computed values, keyed lists, and owner cleanup. It does not establish Solid 2 streaming, hydration, router, or async-transition compatibility.