Repository navigation
Release 0.3.0: action stores - #2
Merged
Merged
Conversation
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Re-export original, current and Draft from Mutative. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Any store can take an action set from `defineActions` instead of a setter:
`useNewStore(initial, set)`, `createContextStore(set)`, and `createTestStore`
for tests. Its actions are then the only way to write.
- Action sets narrow to other action sets, to any depth (recursive data
included), so each part of the state only gets its own actions.
- A set declares what it needs from the levels above it as its methods'
`this`, and the level that narrows to it provides it.
- Called directly, a set's actions are pure functions: state in, next
state out, whether they mutate or replace.
- `NarrowList` takes a level (`actions={appActions.todo}`), or neither a
setter nor a level to only memoize items.
- `useNarrowedStore` and `createNarrowedStoreContext` pick a level from an
action store, handing out its value and actions together.
- The published build is compiled with the React Compiler.
Docs for actions live in docs/actions.md, and examples/todomvc-actions is
TodoMVC written with them. The 0.2 API is unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The action form kept its level stable by setting state during render, so an action set made during render (new every time) looped forever on the first update. It now memoizes the level on the pick function, exactly as the setter form memoizes its setter, and `sameLevel` goes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Action stores: any store can take an action set from `defineActions` instead of a setter, and its actions become the only way to write. Action sets narrow to other action sets to any depth, get what they need from above through a typed `this`, and are pure functions when called directly. `NarrowList`, `useNarrowedStore` and `createNarrowedStoreContext` hand out a level's value and actions together. See docs/actions.md. Also: - `NarrowList` memoizes items with neither a setter nor a level. - `createTestStore` runs a store outside React, for tests. - The published build is compiled with the React Compiler. - Needs TypeScript 5.5 or later (the types use `NoInfer`). - CI runs typecheck, tests, build and the examples' typecheck. The 0.2 API is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- NarrowList: split MemoListItem into ActionListItem and ReadOnlyListItem, rename NarrowListItem to SetterListItem, and give each its own render type instead of a shared `any` one - actions.ts: name the internals for what they do (narrowLevel, actionSetOf, narrowTo, replaceAt, provideContext), fold the narrowing helper into the `narrow` given to `children`, and share Step between runAt and readLevel - Move createTestStore to its own module - Explain the non-obvious bits: the stable setter, the Owned components, the swap that bound actions use, and the lazily bound levels No behaviour or API change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A write through nested narrowed setters used to assign each level's new subtree into its parent's draft, so the root create() made mutative scan the whole assigned tree: the cost of editing one item grew with the size of the entire store (300 ms at 150k items, against 0.2 ms now). Each narrowed setter now hands its parent a mutate recipe that resolves its own path against the draft, so the root makes one create() and only the path is drafted. Swaps assign just the new value at its path; a narrowing that is the whole parent hands a replacement up to the root. Public API and no-op behaviour (same state reference) are unchanged. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FnJDYeu56rddNZDQ8Bp6YS
Stores (useNewStore, context Providers that own their store, and createTestStore) take onPatches, which gets Mutative's patches and inverse patches for each update once React commits it: once per update, in order, and not twice in Strict Mode. applyPatches writes patches through a store's root setter or root actions, and its updates are reported as applied. Bound actions now write through the root setter as one mutate recipe, like narrowed setters since the previous commit, so every write is one create() on the root draft and its patches have exact paths from the root. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Without setStore or actions, NarrowList only wrapped each row in memo, which the React Compiler already gives an ordinary `items.map` of a row component. NarrowList now needs one of the two, and leaving both out is a type error. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Great work on the 0.3.0 release and the Mutative JSON Patch integration! 👏 One small suggestion: could you update the lockfile to Mutative 2.0.1 fixes an array deletion edge case where deleting a no-longer-existing index could unexpectedly grow the array, along with related sparse-array patch handling. Given the new Release: https://github.com/unadlib/mutative/releases/tag/v2.0.1 Thanks for building on Mutative! |
Owner
Author
|
@unadlib On it! Maybe I need to start taking this project a bit more seriously now that you've got your eyes on it 😅 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Any store can now take an action set instead of a setter, for apps that want every change to go through named, typed, testable functions. Its actions are then the only way to write.
useNewStore,createContextStore,createNarrowedStoreContextanduseNarrowedStoreeach also accept an action set (or an action context). New exports aredefineActions,withContext,createTestStoreand four types.narrow((s, id: string) => s.todos.find(…), todoActionSet)), to any depth, recursive data included, so each part of the state only gets its own actions.this, and the level that narrows to it provides it from the parent's draft, in the same update.NarrowList(actions={appActions.todo}),useNarrowedStore(app, (a) => a.todo(id), appActions)and narrowed contexts all hand out a level's value and actions together. Identity is memoized in React, with no caches outside it.createTestStoreruns several in a row.NarrowListcan memoize items with neither a setter nor a level, and the published build is now compiled with the React Compiler.Docs: docs/actions.md. Example: examples/todomvc-actions.
Compatibility
Additive: the 0.2 exports keep their behaviour, and the existing test files are unchanged and pass. Bumps the version to 0.3.0. Needs TypeScript 5.5 or later (5.4 has a
NoInferbug); the README says so.Also in this PR
setStoremust forward both arguments to a store's setter.onPatchesonuseNewStore, owning Providers andcreateTestStoregets Mutative's patches and inverse patches for each committed update (once per update, in order, not twice in Strict Mode), with exact paths from the root.applyPatches(rootSetterOrActions, patches)writes them back and reports them asapplied, for undo and sync. Patches are plain data, so any copy of Mutative can apply them.Testing
src/testing.test.tsx, and a regression test for a render loop fixed in this PR.NarrowLists) ported to action sets in a scratch clone. Its own Playwright suite against the production build: 124 passed, 3 failed, identical to the unported code (the same 3 fail there, and none involve editing).{ get, swap }Maps, actions on levels and replacing actions, swaps and no-ops, undo throughapplyPatches, Strict Mode, batching, Providers, and patches applied by plain Mutative after a JSON round trip.distimports cleanly.examples/todomvc-actionschecked in the browser.🤖 Generated with Claude Code