-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Refactor @xstate/react
to useSyncExternalStore
#2939
Conversation
🦋 Changeset detectedLatest commit: 0268477 The changes in this PR will be included in the next version bump. This PR includes changesets to release 5 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
CodeSee Review Map:Review in an interactive map View more CodeSee Maps Legend |
…g back caching of the `service.initialState`
packages/xstate-react/package.json
Outdated
"react": "18.0.0-rc.0-next-811634762-20220110", | ||
"react-dom": "18.0.0-rc.0-next-811634762-20220110", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Before landing this we should set up tests to run against both React 17 and React 18. It's totally doable - I just didn't bother to do it right now.
This should also only land after React 18 gets finally released.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OTOH - I don't think now that this would add much value here. Stuff that works with React 18 should just work with React 17. So it might be enough to just test with React 18. We can always also revisit adding extra tests for React 17 in the future.
@@ -36,18 +35,15 @@ function toObserver<T>( | |||
}; | |||
} | |||
|
|||
export function useInterpret< | |||
export function useIdleInterpreter< |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not the best name... but naming things is hard™ and naming some hooks is even harder 😅
const service = useInterpret(getMachine, options, listener); | ||
useEffect(() => { | ||
const rehydratedState = options.state; | ||
service.start( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After those changes, useMachine
no longer depends on the useInterpret
. useInterpret
starts the machine in the layout effect but this one has to do that in the passive effect. Maybe we should change the behavior of the useInterpret
too - but I've wanted to avoid changing too much now. I've also wanted to change this for now because how the listener/subscriber gets wired is now different and has to be managed here, in the useMachine
, but it was previously managed by useInterpret
. Technically, we could just not pass any subscriber to the useInterpret
and just subscribe with useSyncExternalStore
here... and maybe that is something that we should do.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, I've added a comment on why we can't use useInterpret
here right now:
47f566c#diff-19f47c3f58ef51d8a79a32343f5c32c02d6d7237ecc29f636cb53846d00424d5R68-R69
const storeSnapshot = useSyncExternalStoreWithSelector( | ||
subscribe, | ||
getSnapshot, | ||
undefined, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is an argument slot for getServerSnapshot
. I'm not yet sure how this should work... maybe we should just use the initialState for it? It somewhat doesn't matter for useMachine
because nothing should be able to change its state between render and the commit phase but all the other hooks are also using the useSyncExternalStore
stuff and the same cannot be said about them - so we should think how this should be handled there.
[] | ||
[service] | ||
); | ||
const storeSnapshot = useSyncExternalStoreWithSelector( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
useMachine
kinda shouldn't have to use useSyncExternalStoreWithSelector
- it could use the simpler useSyncExternalStore
, but to make this possible we'd have to have immutable states without .changed
# Conflicts: # packages/xstate-react/src/index.ts # packages/xstate-react/src/useActor.ts # packages/xstate-react/src/useInterpret.ts # packages/xstate-react/src/useMachine.ts # packages/xstate-react/src/useReactEffectActions.ts # packages/xstate-react/test/useMachine.test.tsx # packages/xstate-react/test/useService.test.tsx
This pull request is automatically built and testable in CodeSandbox. To see build info of the built libraries, click here or the icon next to each commit SHA. Latest deployment of this branch, based on commit 0268477:
|
# Conflicts: # .changeset/red-ants-lick.md # packages/xstate-react/package.json # packages/xstate-react/src/useInterpret.ts # packages/xstate-react/src/useMachine.ts # packages/xstate-react/test/useActor.test.tsx # packages/xstate-react/test/useMachine.test.tsx # packages/xstate-react/test/useSelector.test.tsx
Nice!! |
packages/xstate-react/src/fsm.ts
Outdated
} | ||
}; | ||
}, [service]); | ||
const isEqual = useCallback( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can this just be declared externally?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ah, yes - this one here, for /fsm
, doesn't require access to the local service
- so it can be hoisted
good 👁️ 👍
@@ -462,4 +390,150 @@ describe('interpreter', () => { | |||
|
|||
expect(nextState.actions.map((a) => a.type)).toEqual(['action']); | |||
}); | |||
|
|||
describe('`start` method', () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this block has just been moved to the bottom as tests were sort of mixed and the order in the file didn't match the order in the test report
if (options) { | ||
(service as any)._machine._options = options; | ||
} | ||
}); | ||
|
||
const useServiceResult = useService(service); | ||
|
||
useEffect(() => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
after those changes @xstate/fsm
should be nicely compatible with StrictMode and Fast Refresh (something that can't be said about the core 😬 )
We still can't use the "incoming" machine config from the Fast Refresh though but at least we don't display outdated state after Fast Refresh nor we don't have to fully restart the service so the state is preserved.
const initialStateChanged = | ||
nextState.changed === undefined && | ||
(Object.keys(nextState.children).length > 0 || | ||
typeof prevState.changed === 'boolean'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this additional condition here allows us to "restart" to the initial state after Fast Refresh, whereas previously we were displaying the outdated state that was no longer true for the current, restarted, service
|
||
const { rerender } = render(<App value={1} />); | ||
|
||
expect(actual).toEqual(suiteKey === 'strict' ? [1, 1] : [1]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sigh...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🚢 ⚛️ 1️⃣ 8️⃣
Co-authored-by: David Khourshid <davidkpiano@gmail.com>
Refactor `@xstate/react` to useSyncExternalStore
closes #3196
closes #2821