From 519188af785527195eea15972efbb260289c9979 Mon Sep 17 00:00:00 2001 From: Austin Golding Date: Thu, 17 Oct 2024 02:26:37 -0700 Subject: [PATCH 1/3] [@xstate/solid] Fix context mutation with new values nested in arrays (#5100) * @xstate/solid Fix new array index value not being cloned before insertion into store - Fix #5099 Signed-off-by: Austin Golding * @xstate/solid Fix new array index value not being cloned before insertion into store - Fix #5099 Signed-off-by: Austin Golding * @xstate/solid Update new array index set to use placeholder value if wrappable to prevent mutating original object/array without needing to deep clone Signed-off-by: Austin Golding --------- Signed-off-by: Austin Golding --- .changeset/strong-keys-work.md | 5 + packages/xstate-solid/src/createImmutable.ts | 26 ++-- packages/xstate-solid/test/useActor.test.tsx | 129 ++++++++++++++++++- 3 files changed, 151 insertions(+), 9 deletions(-) create mode 100644 .changeset/strong-keys-work.md diff --git a/.changeset/strong-keys-work.md b/.changeset/strong-keys-work.md new file mode 100644 index 0000000000..ae3757f543 --- /dev/null +++ b/.changeset/strong-keys-work.md @@ -0,0 +1,5 @@ +--- +'@xstate/solid': patch +--- + +When setting new array indexes, if the value is an object/array, use placeholder empty value to prevent mutation of original machine context diff --git a/packages/xstate-solid/src/createImmutable.ts b/packages/xstate-solid/src/createImmutable.ts index 27fb81b73b..4ba50c68da 100644 --- a/packages/xstate-solid/src/createImmutable.ts +++ b/packages/xstate-solid/src/createImmutable.ts @@ -12,6 +12,14 @@ const resolvePath = (path: any[], obj = {}): unknown => { return current; }; +const getWrappablePlaceholder = (value: any) => { + if (!isWrappable(value)) return value; + if (Array.isArray(value)) { + return []; + } + return {}; +}; + const updateStore = ( nextStore: Store, prevStore: Store, @@ -53,6 +61,13 @@ const updateStore = ( const smallestSize = Math.min(prev.length, next.length); const largestSize = Math.max(next.length, prev.length); + // Update new or now undefined indexes + if (newIndices !== 0) { + for (let newEnd = smallestSize; newEnd <= largestSize - 1; newEnd++) { + set(...path, newEnd, getWrappablePlaceholder(next[newEnd])); + } + } + // Diff array for (let start = 0, end = largestSize - 1; start <= end; start++, end--) { diff(next[start], prev[start], [...path, start] as Path); @@ -60,14 +75,9 @@ const updateStore = ( diff(next[end], prev[end], [...path, end] as Path); } - // Update new or now undefined indexes - if (newIndices !== 0) { - for (let newEnd = smallestSize; newEnd <= largestSize - 1; newEnd++) { - set(...path, newEnd, next[newEnd]); - } - if (prev.length > next.length) { - set(...path, 'length', next.length); - } + // Update length if it has changed + if (prev.length !== next.length) { + set(...path, 'length', next.length); } } else { // Update new values diff --git a/packages/xstate-solid/test/useActor.test.tsx b/packages/xstate-solid/test/useActor.test.tsx index 539d31108c..06d2ac5d79 100644 --- a/packages/xstate-solid/test/useActor.test.tsx +++ b/packages/xstate-solid/test/useActor.test.tsx @@ -8,7 +8,8 @@ import { mergeProps, on, onCleanup, - onMount + onMount, + For } from 'solid-js'; import { fireEvent, render, screen, waitFor } from 'solid-testing-library'; import { @@ -685,6 +686,132 @@ describe('useActor', () => { expect(countEl.textContent).toEqual('1'); }); + it('Moving objects between arrays should not mutate context', () => { + const stackMachine = createMachine({ + types: {} as { + context: { current: { value: string }[]; done: { value: string }[] }; + events: { type: 'next' } | { type: 'prev' }; + }, + id: 'stack', + initial: 'active', + context: { + current: [ + { value: 'Stack #1' }, + { value: 'Stack #2' }, + { value: 'Stack #3' } + ], + done: [] + }, + states: { + active: { + on: { + next: { + guard: ({ context }) => context.current.length > 0, + actions: assign(({ context }) => { + const [first, ...rest] = context.current; + return { + current: rest, + done: [...context.done, first] + }; + }) + }, + prev: { + guard: ({ context }) => context.done.length > 0, + actions: assign(({ context }) => { + const rest = context.done.slice(0, -1); + const last = context.done.at(-1); + return { + current: last ? [last, ...context.current] : context.current, + done: rest + }; + }) + } + } + } + } + }); + + function App() { + const [snapshot, send] = useActor(stackMachine); + + return ( + <> +
+
+ Current +
    + + {(item) => { + return
  • {item.value}
  • ; + }} +
    +
+
+ +
+ Done +
    + + {(item) => { + return
  • {item.value}
  • ; + }} +
    +
+
+
+
+ {' '} + +
+ + ); + } + + render(() => ); + + const firstToDoneButton = screen.getByTestId( + 'first-current-to-done-button' + ); + const lastToCurrentButton = screen.getByTestId( + 'last-done-to-current-button' + ); + const currentList = screen.getByTestId('current-list'); + const doneList = screen.getByTestId('done-list'); + + expect(currentList.children.length).toBe(3); + expect(doneList.children.length).toBe(0); + + fireEvent.click(firstToDoneButton); + fireEvent.click(firstToDoneButton); + fireEvent.click(firstToDoneButton); + + expect(currentList.children.length).toBe(0); + expect(doneList.children.length).toBe(3); + expect(doneList.innerHTML).toBe( + '
  • Stack #1
  • Stack #2
  • Stack #3
  • ' + ); + + fireEvent.click(lastToCurrentButton); + fireEvent.click(lastToCurrentButton); + fireEvent.click(lastToCurrentButton); + + expect(currentList.children.length).toBe(3); + expect(doneList.children.length).toBe(0); + expect(currentList.innerHTML).toBe( + '
  • Stack #1
  • Stack #2
  • Stack #3
  • ' + ); + }); + it('should capture initial actions', () => { let count = 0; From b730b298994aa00e0a76783d747c8063853d389e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 17 Oct 2024 07:48:27 -0400 Subject: [PATCH 2/3] Version Packages (#5106) Co-authored-by: github-actions[bot] --- .changeset/strong-keys-work.md | 5 ----- packages/xstate-solid/CHANGELOG.md | 6 ++++++ packages/xstate-solid/package.json | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) delete mode 100644 .changeset/strong-keys-work.md diff --git a/.changeset/strong-keys-work.md b/.changeset/strong-keys-work.md deleted file mode 100644 index ae3757f543..0000000000 --- a/.changeset/strong-keys-work.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@xstate/solid': patch ---- - -When setting new array indexes, if the value is an object/array, use placeholder empty value to prevent mutation of original machine context diff --git a/packages/xstate-solid/CHANGELOG.md b/packages/xstate-solid/CHANGELOG.md index 932109a74a..0d9a61758d 100644 --- a/packages/xstate-solid/CHANGELOG.md +++ b/packages/xstate-solid/CHANGELOG.md @@ -1,5 +1,11 @@ # @xstate/solid +## 0.2.3 + +### Patch Changes + +- [#5100](https://github.com/statelyai/xstate/pull/5100) [`519188af785527195eea15972efbb260289c9979`](https://github.com/statelyai/xstate/commit/519188af785527195eea15972efbb260289c9979) Thanks [@GoldingAustin](https://github.com/GoldingAustin)! - When setting new array indexes, if the value is an object/array, use placeholder empty value to prevent mutation of original machine context + ## 0.2.2 ### Patch Changes diff --git a/packages/xstate-solid/package.json b/packages/xstate-solid/package.json index 8385876e45..7301196d39 100644 --- a/packages/xstate-solid/package.json +++ b/packages/xstate-solid/package.json @@ -1,6 +1,6 @@ { "name": "@xstate/solid", - "version": "0.2.2", + "version": "0.2.3", "description": "XState tools for SolidJS", "keywords": [ "state", From d67b71dd25d457a2a59f2c943db13f50fab7ec3d Mon Sep 17 00:00:00 2001 From: David Khourshid Date: Mon, 28 Oct 2024 09:55:54 -0400 Subject: [PATCH 3/3] Add peer dependency support for React 19 (#5109) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add peer dependency support for React 19 * Changeset * Update packages/xstate-react/package.json Co-authored-by: Mateusz Burzyński * Update packages/xstate-store/package.json Co-authored-by: Mateusz Burzyński --------- Co-authored-by: Mateusz Burzyński --- .changeset/four-countries-serve.md | 6 ++++++ packages/xstate-react/package.json | 2 +- packages/xstate-store/package.json | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) create mode 100644 .changeset/four-countries-serve.md diff --git a/.changeset/four-countries-serve.md b/.changeset/four-countries-serve.md new file mode 100644 index 0000000000..3a8c5f4b88 --- /dev/null +++ b/.changeset/four-countries-serve.md @@ -0,0 +1,6 @@ +--- +'@xstate/react': patch +'@xstate/store': patch +--- + +Add React 19 as a peer dependency diff --git a/packages/xstate-react/package.json b/packages/xstate-react/package.json index 9417648228..fa92bd0dd2 100644 --- a/packages/xstate-react/package.json +++ b/packages/xstate-react/package.json @@ -54,7 +54,7 @@ "url": "https://github.com/statelyai/xstate/issues" }, "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-0", "xstate": "workspace:^" }, "peerDependenciesMeta": { diff --git a/packages/xstate-store/package.json b/packages/xstate-store/package.json index ef2b93cf34..45d3494253 100644 --- a/packages/xstate-store/package.json +++ b/packages/xstate-store/package.json @@ -69,7 +69,7 @@ "xstate": "workspace:^" }, "peerDependencies": { - "react": "^18.2.0", + "react": "^18.2.0 || ^19.0.0-0", "solid-js": "^1.7.6" }, "peerDependenciesMeta": {