Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions .changeset/flux-store-solid2-migration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
---
"@solid-primitives/flux-store": major
---

Migrate to Solid.js v2.0 (beta.14)

## Breaking Changes

**Peer dependencies**: `solid-js@^2.0.0-beta.14` is now required.

**Store setter is now draft-first.** The `setState` function received in the `actions` callback no longer accepts path-style arguments. Instead, pass a draft mutator function:

```ts
// Before (Solid 1.x)
actions: setState => ({
increment(by = 1) {
setState("value", p => p + by);
},
reset() {
setState("value", 0);
},
})

// After (Solid 2.0)
actions: setState => ({
increment(by = 1) {
setState(s => { s.value += by; });
},
reset() {
setState(s => { s.value = 0; });
},
})
```

**`produce` helper removed.** Solid 2.0 stores use draft-first mutation by default, so `produce` is no longer necessary or available. Replace any `setState(produce(s => ...))` calls with `setState(s => ...)`.

**`batch` wrapper removed from `createAction`.** All writes in Solid 2.0 are auto-batched, so the explicit `batch()` wrap has been removed from `createAction`. Actions remain `untrack`ed.

**Import paths updated:** `createStore` and `StoreSetter` (formerly `SetStoreFunction`) are now imported from `solid-js` directly (store types were merged into the main package).
123 changes: 70 additions & 53 deletions packages/flux-store/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@
[![version](https://img.shields.io/npm/v/@solid-primitives/flux-store?style=for-the-badge)](https://www.npmjs.com/package/@solid-primitives/flux-store)
[![stage](https://img.shields.io/endpoint?style=for-the-badge&url=https%3A%2F%2Fraw.githubusercontent.com%2Fsolidjs-community%2Fsolid-primitives%2Fmain%2Fassets%2Fbadges%2Fstage-0.json)](https://github.com/solidjs-community/solid-primitives#contribution-process)

A library for creating Solid stores with implementing state management through explicit getters for reads and actions for writes.
A library for creating Solid stores that enforce a one-way data flow through explicit getters for reads and actions for writes.

- [`createFluxStore`](#createfluxstore) — Creates a store instance with explicit getters and actions.
- [`createFluxStoreFactory`](#createfluxstorefactory) — Create a `FluxStore` encapsulated in a factory function for reusable store implementation.
- [`createFluxStoreFactory`](#createfluxstorefactory) — Creates a `FluxStore` encapsulated in a factory function for reusable store instances.
- [`createActions`](#createactions) — Wraps a record of functions so each runs untracked.
- [`createAction`](#createaction) — Wraps a single function so it runs untracked.

## Installation

Expand All @@ -25,93 +27,108 @@ pnpm add @solid-primitives/flux-store

## `createFluxStore`

Creates a `FluxStore` instance - a solid store that implements state management through explicit getters for reads and actions for writes.
Creates a `FluxStore` — a Solid store that separates reads (`getters`) from writes (`actions`).

### How to use it

`createFluxStore` takes two arguments:

- `initialState` - the initial state of the store.

- `createMethods` - object containing functions to create getters and/or actions.

- `getters` - functions that return a value from the store's state.
- `actions` - untracked and batched functions that update the store's state.
- `initialState` — the initial state object.
- `createMethods` — object with optional `getters` and required `actions` factory functions.
- `getters(state)` — return a record of functions that read from the reactive store proxy.
- `actions(setState, state)` — return a record of mutation functions. `setState` uses draft-first mutations: pass a function that mutates the draft object directly.

```ts
import { createFluxStore } from "@solid-primitives/flux-store";

const counterState = createFluxStore(
// initial state
{
value: 5,
},
const counterStore = createFluxStore(
{ value: 5 },
{
// reads
getters: state => ({
count() {
return state.value;
},
count: () => state.value,
isNegative: () => state.value < 0,
}),
// writes
actions: setState => ({
increment(by = 1) {
setState("value", p => p + by);
setState(s => { s.value += by; });
},
reset() {
setState("value", 0);
setState(s => { s.value = 0; });
},
}),
},
);

// read
counterState.getters.count(); // => 5

// write
counterState.actions.increment();
counterState.getters.count(); // => 6
counterStore.getters.count(); // => 5
counterStore.actions.increment();
counterStore.getters.count(); // => 6
counterStore.actions.reset();
counterStore.getters.count(); // => 0
```

## `createFluxStoreFactory`

Creates a [`FluxStore`](#createfluxstore) encapsulated in a factory function for reusable store implementation.
Creates a reusable factory function that produces independent `FluxStore` instances from the same schema, with an optional initial-state override per instance.

### How to use it

```ts
const createToggleState = createFluxStoreFactory(
// initial state
import { createFluxStoreFactory } from "@solid-primitives/flux-store";

const createToggleStore = createFluxStoreFactory(
{ value: false },
{
value: false,
getters: state => ({
isOn: () => state.value,
}),
actions: setState => ({
toggle() {
setState(s => { s.value = !s.value; });
},
}),
},
// reads
getters: state => ({
isOn() {
return state.value;
},
}),
// writes
actions: setState => ({
toggle() {
setState("value", p => !p);
},
}),
);

// Each call creates an isolated store instance
const toggleA = createToggleStore({ value: true });
const toggleB = createToggleStore();

// state factory can be reused in different components
const toggleState = createToggleState(
// initial state can be overridden
{ value: true },
);
toggleA.getters.isOn(); // => true
toggleB.getters.isOn(); // => false

toggleA.actions.toggle();
toggleA.getters.isOn(); // => false
toggleB.getters.isOn(); // => false (unaffected)
```

The factory accepts an optional override as a plain object or a function:

```ts
const store1 = createToggleStore({ value: true });
const store2 = createToggleStore(defaults => ({ ...defaults, value: true }));
```

## `createActions`

Wraps each function in a record with `createAction` and returns a new object of the same shape. Useful for applying the untracked wrapper to a batch of functions at once.

// read
toggleState.getters.isOn(); // => true
```ts
import { createActions } from "@solid-primitives/flux-store";

const actions = createActions({
increment: () => setCount(c => c + 1),
reset: () => setCount(0),
});
```

## `createAction`

Wraps a single function so its body runs inside `untrack` — reactive reads inside will not register dependencies and writes will not throw inside owned scopes.

```ts
import { createAction } from "@solid-primitives/flux-store";

// write
toggleState.actions.toggle();
toggleState.getters.isOn(); // => false
const increment = createAction(() => setCount(c => c + 1));
```

## Demo
Expand Down
6 changes: 3 additions & 3 deletions packages/flux-store/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"stage": 0,
"list": [
"createFluxStore",
"createFluxFactory",
"createFluxStoreFactory",
"createActions",
"createAction"
],
Expand Down Expand Up @@ -57,10 +57,10 @@
"test:ssr": "pnpm run vitest --mode ssr"
},
"peerDependencies": {
"solid-js": "^1.6.12"
"solid-js": "^2.0.0-beta.14"
},
"typesVersions": {},
"devDependencies": {
"solid-js": "^1.9.7"
"solid-js": "2.0.0-beta.14"
}
}
43 changes: 23 additions & 20 deletions packages/flux-store/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { batch, untrack } from "solid-js";
import type { SetStoreFunction } from "solid-js/store";
import { createStore } from "solid-js/store";
import { untrack } from "solid-js";
import type { Store, StoreSetter } from "solid-js";
import { createStore } from "solid-js";

/**
* Type alias for any function with any number of arguments and any return type.
Expand All @@ -15,13 +15,13 @@ export type AnyFunctionsRecord = { readonly [K in string]: AnyFunction };
export type Actions<T extends AnyFunctionsRecord> = { readonly [K in keyof T]: T[K] };

/**
* Identify function creating an action - function for mutating the state.
* Actions are `batch`ed and `untrack`ed by default - no need to wrap them in `batch` and `untrack`.
* Wraps a function so its body runs untracked — reads inside will not register reactive
* dependencies and writes will not throw inside owned scopes.
* @param fn the function to wrap
* @returns function of the same signature as `fn` but wrapped in `batch` and `untrack`
* @returns function of the same signature as `fn` but running untracked
*/
export function createAction<T extends AnyFunction>(fn: T): T {
return ((...args) => batch(() => untrack(() => fn(...args)))) as T;
return ((...args) => untrack(() => fn(...args))) as T;
}

/**
Expand Down Expand Up @@ -87,9 +87,9 @@ export type FluxFactory<
* getters: state => ({
* count: () => state.value,
* }),
* actions: (setState, state) => ({
* increment: () => setState(val => ({ ...val, value: val.value + 1 })),
* reset: () => setState("value", 0),
* actions: setState => ({
* increment: (by = 1) => setState(s => { s.value += by; }),
* reset: () => setState(s => { s.value = 0; }),
* })
* });
*
Expand All @@ -110,13 +110,13 @@ export function createFluxStore<
initialState: TState,
createMethods: {
getters: (state: TState) => TGetters;
actions: (setState: SetStoreFunction<TState>, state: TState) => TActions;
actions: (setState: StoreSetter<TState>, state: TState) => TActions;
},
): FluxStore<TState, TActions, TGetters>;
export function createFluxStore<TState extends object, TActions extends AnyFunctionsRecord>(
initialState: TState,
createMethods: {
actions: (setState: SetStoreFunction<TState>, state: TState) => TActions;
actions: (setState: StoreSetter<TState>, state: TState) => TActions;
},
): FluxStore<TState, TActions>;
export function createFluxStore<
Expand All @@ -127,10 +127,13 @@ export function createFluxStore<
initialState: TState,
createMethods: {
getters?: (state: TState) => TGetters;
actions: (setState: SetStoreFunction<TState>, state: TState) => TActions;
actions: (setState: StoreSetter<TState>, state: TState) => TActions;
},
): FluxStore<TState, TActions, TGetters | {}> {
const [state, setState] = createStore(initialState);
const [state, setState] = createStore(initialState as any) as unknown as [
Store<TState>,
StoreSetter<TState>,
];
return {
state,
getters: createMethods.getters ? createMethods.getters(state) : {},
Expand All @@ -154,9 +157,9 @@ export function createFluxStore<
* getters: state => ({
* count: () => state.value,
* }),
* actions: (setState, state) => ({
* increment: () => setState(val => ({ ...val, value: val.value + 1 })),
* reset: () => setState("value", 0),
* actions: setState => ({
* increment: (by = 1) => setState(s => { s.value += by; }),
* reset: () => setState(s => { s.value = 0; }),
* })
* });
*
Expand All @@ -183,13 +186,13 @@ export function createFluxStoreFactory<
fallbackState: TState,
createMethods: {
getters: (state: TState) => TGetters;
actions: (setState: SetStoreFunction<TState>, state: TState) => TActions;
actions: (setState: StoreSetter<TState>, state: TState) => TActions;
},
): FluxFactory<TState, TActions, TGetters>;
export function createFluxStoreFactory<TState extends object, TActions extends AnyFunctionsRecord>(
fallbackState: TState,
createMethods: {
actions: (setState: SetStoreFunction<TState>, state: TState) => TActions;
actions: (setState: StoreSetter<TState>, state: TState) => TActions;
},
): FluxFactory<TState, TActions>;
export function createFluxStoreFactory<
Expand All @@ -200,7 +203,7 @@ export function createFluxStoreFactory<
fallbackState: TState,
createMethods: {
getters?: (state: TState) => TGetters;
actions: (setState: SetStoreFunction<TState>, state: TState) => TActions;
actions: (setState: StoreSetter<TState>, state: TState) => TActions;
},
): FluxFactory<TState, TActions, TGetters | {}> {
return initialValueOverride =>
Expand Down
Loading