Skip to content

Commit

Permalink
Merge branch 'main' into davidkpiano/transition
Browse files Browse the repository at this point in the history
  • Loading branch information
davidkpiano committed Nov 12, 2024
2 parents c04c12c + d67b71d commit 9772108
Show file tree
Hide file tree
Showing 7 changed files with 161 additions and 12 deletions.
6 changes: 6 additions & 0 deletions .changeset/four-countries-serve.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@xstate/react': patch
'@xstate/store': patch
---

Add React 19 as a peer dependency
2 changes: 1 addition & 1 deletion packages/xstate-react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
6 changes: 6 additions & 0 deletions packages/xstate-solid/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
2 changes: 1 addition & 1 deletion packages/xstate-solid/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@xstate/solid",
"version": "0.2.2",
"version": "0.2.3",
"description": "XState tools for SolidJS",
"keywords": [
"state",
Expand Down
26 changes: 18 additions & 8 deletions packages/xstate-solid/src/createImmutable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = <Path extends unknown[]>(
nextStore: Store<any>,
prevStore: Store<any>,
Expand Down Expand Up @@ -53,21 +61,23 @@ const updateStore = <Path extends unknown[]>(
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);
if (start === end) break;
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
Expand Down
129 changes: 128 additions & 1 deletion packages/xstate-solid/test/useActor.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import {
mergeProps,
on,
onCleanup,
onMount
onMount,
For
} from 'solid-js';
import { fireEvent, render, screen, waitFor } from 'solid-testing-library';
import {
Expand Down Expand Up @@ -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 (
<>
<div style={{ display: 'flex', gap: '24px' }}>
<div>
Current
<ul data-testid="current-list">
<For each={snapshot.context.current}>
{(item) => {
return <li>{item.value}</li>;
}}
</For>
</ul>
</div>

<div>
Done
<ul data-testid="done-list">
<For each={snapshot.context.done}>
{(item) => {
return <li>{item.value}</li>;
}}
</For>
</ul>
</div>
</div>
<div>
<button
data-testid="first-current-to-done-button"
onClick={() => send({ type: 'next' })}
>
Remove first
</button>{' '}
<button
data-testid="last-done-to-current-button"
onClick={() => send({ type: 'prev' })}
>
Return last
</button>
</div>
</>
);
}

render(() => <App />);

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(
'<li>Stack #1</li><li>Stack #2</li><li>Stack #3</li>'
);

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(
'<li>Stack #1</li><li>Stack #2</li><li>Stack #3</li>'
);
});

it('should capture initial actions', () => {
let count = 0;

Expand Down
2 changes: 1 addition & 1 deletion packages/xstate-store/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down

0 comments on commit 9772108

Please sign in to comment.