

How to manage global state with XState and React
source link: https://dev.to/mpocock1/how-to-manage-global-state-with-xstate-and-react-3if5
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.


How to manage global state with XState and React
May 25
・3 min read
Many React applications follow the Flux architecture popularised by Redux. This setup can be characterised by a few key ideas:
- It uses a single object at the top of your app which stores all application state, often called the store.
- It provides a single
dispatch
function which can be used to send messages up to the store. Redux calls theseactions
, but I'll be calling themevents
- as they're known in XState. - How the store responds to these messages from the app are expressed in pure functions - most often in reducers.
This article won't go into depth on whether the Flux architecture is a good idea. David Khourshid's article Redux is half a pattern goes into great detail here. For the purposes of this article, we're going to assume that you like having a global store, and you want to replicate it in XState.
There are many reasons for wanting to do so. XState is second-to-none when it comes to managing complex asynchronous behaviour and modelling difficult problems. Managing this in Redux apps usually involves middleware: either redux-thunk, redux-loop or redux-saga. Choosing XState gives you a first-class way to manage complexity.
A globally available store
To mimic Redux's globally-available store, we're going to use React context. React context can be a tricky tool to work with - if you pass in values which change too often, in can result in re-renders all the way down the tree. That means we need to pass in values which change as little as possible.
Luckily, XState gives us a first-class way to do that.
import React, { createContext } from 'react';
import { useInterpret } from '@xstate/react';
import { authMachine } from './authMachine';
import { InterpreterFrom } from 'xstate';
interface GlobalStateContextType {
authService: InterpreterFrom<typeof authMachine>;
}
export const GlobalStateContext = createContext(
// Typed this way to avoid TS errors,
// looks odd I know
{} as GlobalStateContextType,
);
export const GlobalStateProvider = (props) => {
const authService = useInterpret(authMachine);
return (
<GlobalStateContext.Provider value={{ authService }}>
{props.children}
</GlobalStateContext.Provider>
);
};
Using useInterpret
returns a service
, which is a static reference to the running machine which can be subscribed to. This value never changes, so we don't need to worry about wasted re-renders.
Utilising context
Further down the tree, you can subscribe to the service like this:
import React, { useContext } from 'react';
import { GlobalStateContext } from './globalState';
import { useService } from '@xstate/react';
export const SomeComponent = (props) => {
const globalServices = useContext(GlobalStateContext);
const [state] = useService(globalServices.authService);
return state.matches('loggedIn') ? 'Logged In' : 'Logged Out';
};
The useService
hook listens for whenever the service changes, and updates the state
value.
Improving Performance
There's an issue with the implementation above - this will update the component for any change to the service. Redux offers tools for deriving state using selectors - functions which restrict which parts of the state can result in components re-rendering.
Luckily, XState provides that too.
import React, { useContext } from 'react';
import { GlobalStateContext } from './globalState';
import { useSelector } from '@xstate/react';
const selector = (state) => {
return state.matches('loggedIn');
};
export const SomeComponent = (props) => {
const globalServices = useContext(GlobalStateContext);
const isLoggedIn = useSelector(globalServices.authService, selector);
return isLoggedIn ? 'Logged In' : 'Logged Out';
};
Now, this component will only re-render when state.matches('loggedIn')
returns a different value. This is my recommended approach over useService
for when you want to optimise performance.
Dispatching events
For dispatching events to the global store, you can call a service's send
function directly.
import React, { useContext } from 'react';
import { GlobalStateContext } from './globalState';
export const SomeComponent = (props) => {
const globalServices = useContext(GlobalStateContext);
return (
<button onClick={() => globalServices.authService.send('LOG_OUT')}>
Log Out
</button>
);
};
Note that you don't need to call useService
for this, it's available right on the context.
Deviations from Flux
Keen-eyed readers may spot that this implementation is slightly different from Flux. For instance - instead of a single global store, one might have several running machines at once: authService
, dataCacheService
, and globalTimeoutService
. Each of them have their own send
attributes, too - so you're not calling a global dispatch.
These changes can be worked around. One could create a synthetic send
inside the global store which called all the services' send
function manually. But personally, I prefer knowing exactly which services my messages are being passed to, and it avoids having to keep events globally namespaced.
Summary
XState can work beautifully as a global store for a React application. It keeps application logic co-located, treats side effects as first-class citizens, and offers good performance with useSelector
. You should choose this approach if you're keen on the Flux architecture but feel your app's logic is getting out of hand.
Recommend
-
13
Coordinating Svelte Animations With XState Adam Rackis on Apr 7, 2021 Take the pain out of building site search with the Algolia hosted A...
-
8
BackXState: The Solution to All Your App State ProblemsJune 4th, 2021 · 7 min read
-
6
react-native-persist-state Manage persist state with simple API in React Native ✨ Manage persist state with simple API Shared state across components using Hook Ability to access state outside of component...
-
4
XState can be used wherever JavaScript runs, whether on the backend or frontend. Because the code it creates can be visualized, it's great at handling complex use cases - being able to see what a complex piece of code does can be extremely useful....
-
6
Simplify your full-stack applications with XStateJuly 06, 2022Daniel Belo Gonçalves8 min read
-
7
@simplystated/f-of-xstate · Tools for operating on XState state machines as data. Query and update your statechart structure.
-
2
Introduction to State Machines in React with XStateInsights on state management with XState while developing a React Application
-
4
Manage UI State with XState — Inspired by Finite State Machines
-
8
XState Docs GitHub
-
4
Note Simple Notes App Using React Native, NativeWind, XState Nov 06, 2023 1 min read
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK