Research
Security News
Malicious npm Packages Inject SSH Backdoors via Typosquatted Libraries
Socket’s threat research team has detected six malicious npm packages typosquatting popular libraries to insert SSH backdoors.
@xstate/react
Advanced tools
@xstate/react is a library that provides React hooks for using XState, a state machine and statechart library. It allows you to manage complex state logic in a more structured and predictable way by using finite state machines and statecharts.
Creating and Using State Machines
This feature allows you to create and use state machines in your React components. The code sample demonstrates a simple toggle button that switches between 'inactive' and 'active' states.
import { useMachine } from '@xstate/react';
import { createMachine } from 'xstate';
const toggleMachine = createMachine({
id: 'toggle',
initial: 'inactive',
states: {
inactive: { on: { TOGGLE: 'active' } },
active: { on: { TOGGLE: 'inactive' } }
}
});
function ToggleButton() {
const [state, send] = useMachine(toggleMachine);
return (
<button onClick={() => send('TOGGLE')}>
{state.matches('inactive') ? 'Off' : 'On'}
</button>
);
}
Using Context for Extended State
This feature allows you to use context for extended state in your state machines. The code sample demonstrates a counter that increments and decrements a count value stored in the machine's context.
import { useMachine } from '@xstate/react';
import { createMachine } from 'xstate';
const counterMachine = createMachine({
id: 'counter',
initial: 'active',
context: { count: 0 },
states: {
active: {
on: {
INCREMENT: { actions: 'increment' },
DECREMENT: { actions: 'decrement' }
}
}
}
}, {
actions: {
increment: (context) => context.count++,
decrement: (context) => context.count--
}
});
function Counter() {
const [state, send] = useMachine(counterMachine);
return (
<div>
<p>{state.context.count}</p>
<button onClick={() => send('INCREMENT')}>Increment</button>
<button onClick={() => send('DECREMENT')}>Decrement</button>
</div>
);
}
Invoking Services
This feature allows you to invoke services (e.g., API calls) within your state machines. The code sample demonstrates a state machine that fetches data from an API and handles loading, success, and failure states.
import { useMachine } from '@xstate/react';
import { createMachine, assign } from 'xstate';
const fetchMachine = createMachine({
id: 'fetch',
initial: 'idle',
context: { data: null, error: null },
states: {
idle: { on: { FETCH: 'loading' } },
loading: {
invoke: {
src: 'fetchData',
onDone: { target: 'success', actions: assign({ data: (context, event) => event.data }) },
onError: { target: 'failure', actions: assign({ error: (context, event) => event.data }) }
}
},
success: { on: { FETCH: 'loading' } },
failure: { on: { FETCH: 'loading' } }
}
}, {
services: {
fetchData: () => fetch('/api/data').then(response => response.json())
}
});
function FetchData() {
const [state, send] = useMachine(fetchMachine);
return (
<div>
{state.matches('idle') && <button onClick={() => send('FETCH')}>Fetch Data</button>}
{state.matches('loading') && <p>Loading...</p>}
{state.matches('success') && <pre>{JSON.stringify(state.context.data, null, 2)}</pre>}
{state.matches('failure') && <p>Error: {state.context.error}</p>}
</div>
);
}
Redux is a popular state management library for JavaScript applications. It provides a centralized store for state and uses actions and reducers to manage state changes. Unlike @xstate/react, which uses state machines and statecharts, Redux relies on a more traditional approach with a single global state and pure functions to handle state transitions.
MobX is a state management library that makes state observable and automatically updates the UI when the state changes. It uses reactive programming principles and provides a more flexible and less boilerplate-heavy approach compared to @xstate/react. While @xstate/react focuses on finite state machines, MobX emphasizes reactivity and simplicity.
Recoil is a state management library for React that provides a more fine-grained approach to state management. It allows you to create atoms (pieces of state) and selectors (derived state) that can be used in your components. Recoil offers a more granular and flexible way to manage state compared to the structured approach of state machines in @xstate/react.
xstate
and @xstate/react
:npm i xstate @xstate/react
useMachine
hook:import { useMachine } from '@xstate/react';
import { Machine } from 'xstate';
const toggleMachine = Machine({
id: 'toggle',
initial: 'inactive',
states: {
inactive: {
on: { TOGGLE: 'active' }
},
active: {
on: { TOGGLE: 'inactive' }
}
}
});
export const Toggler = () => {
const [state, send] = useMachine(toggleMachine);
return (
<button onClick={() => send('TOGGLE')}>
{state.value === 'inactive'
? 'Click to activate'
: 'Active! Click to deactivate'}
</button>
);
};
useMachine(machine, options?)
A React hook that interprets the given machine
and starts a service that runs for the lifetime of the component.
Arguments
machine
- An XState machine.options
(optional) - Interpreter options OR one of the following Machine Config options: guards
, actions
, activities
, services
, delays
, immediate
, context
, or state
.Returns a tuple of [state, send, service]
:
state
- Represents the current state of the machine as an XState State
object.send
- A function that sends events to the running service.service
- The created service.useService(service)
A React hook that subscribes to state changes from an existing service.
Arguments
service
- An XState service.Returns a tuple of [state, send]
:
state
- Represents the current state of the service as an XState State
object.send
- A function that sends events to the running service.asEffect(action)
Ensures that the action
is executed as an effect in useEffect
, rather than being immediately executed.
Arguments
action
- An action function (e.g., (context, event) => { alert(context.message) })
)Returns a special action function that wraps the original so that useMachine
knows to execute it in useEffect
.
Example
const machine = createMachine({
initial: 'focused',
states: {
focused: {
entry: 'focus'
}
}
});
const Input = () => {
const inputRef = useRef(null);
const [state, send] = useMachine(machine, {
actions: {
focus: asEffect((context, event) => {
inputRef.current && inputRef.current.focus();
})
}
});
return <input ref={inputRef} />;
};
asLayoutEffect(action)
Ensures that the action
is executed as an effect in useLayoutEffect
, rather than being immediately executed.
Arguments
action
- An action function (e.g., (context, event) => { alert(context.message) })
)Returns a special action function that wraps the original so that useMachine
knows to execute it in useLayoutEffect
.
useMachine(machine)
with @xstate/fsm
A React hook that interprets the given finite state machine
from [@xstate/fsm
] and starts a service that runs for the lifetime of the component.
This special useMachine
hook is imported from @xstate/react/lib/fsm
Arguments
machine
- An XState finite state machine (FSM).options
- An optional options
object.Returns a tuple of [state, send, service]
:
state
- Represents the current state of the machine as an @xstate/fsm
StateMachine.State
object.send
- A function that sends events to the running service.service
- The created @xstate/fsm
service.Example
import { useEffect } from 'react';
import { useMachine } from `@xstate/react/lib/fsm`;
import { createMachine } from '@xstate/fsm';
const context = {
data: undefined
};
const fetchMachine = createMachine({
id: 'fetch',
initial: 'idle',
context,
states: {
idle: {
on: { FETCH: 'loading' }
},
loading: {
entry: ['load'],
on: {
RESOLVE: {
target: 'success',
actions: assign({
data: (context, event) => event.data
})
}
}
},
success: {}
}
});
const Fetcher = ({ onFetch = () => new Promise(res => res('some data')) }) => {
const [state, send] = useMachine(fetchMachine, {
actions: {
load: () => {
onFetch().then(res => {
send({ type: 'RESOLVE', data: res });
});
}
}
});
switch (state.value) {
case 'idle':
return <button onClick={_ => send('FETCH')}>Fetch</button>;
case 'loading':
return <div>Loading...</div>;
case 'success':
return (
<div>
Success! Data: <div data-testid="data">{state.context.data}</div>
</div>
);
default:
return null;
}
};
Existing machines can be configured by passing the machine options as the 2nd argument of useMachine(machine, options)
.
Example: the 'fetchData'
service and 'notifySuccess'
action are both configurable:
const fetchMachine = Machine({
id: 'fetch',
initial: 'idle',
context: {
data: undefined,
error: undefined
},
states: {
idle: {
on: { FETCH: 'loading' }
},
loading: {
invoke: {
src: 'fetchData',
onDone: {
target: 'success',
actions: assign({
data: (_, event) => event.data
})
},
onError: {
target: 'failure',
actions: assign({
error: (_, event) => event.data
})
}
}
},
success: {
entry: 'notifySuccess',
type: 'final'
},
failure: {
on: {
RETRY: 'loading'
}
}
}
});
const Fetcher = ({ onResolve }) => {
const [state, send] = useMachine(fetchMachine, {
actions: {
notifySuccess: (ctx) => onResolve(ctx.data)
},
services: {
fetchData: (_, e) =>
fetch(`some/api/${e.query}`).then((res) => res.json())
}
});
switch (state.value) {
case 'idle':
return (
<button onClick={() => send('FETCH', { query: 'something' })}>
Search for something
</button>
);
case 'loading':
return <div>Searching...</div>;
case 'success':
return <div>Success! Data: {state.context.data}</div>;
case 'failure':
return (
<>
<p>{state.context.error.message}</p>
<button onClick={() => send('RETRY')}>Retry</button>
</>
);
default:
return null;
}
};
When using hierarchical and parallel machines, the state values will be objects, not strings. In this case, it is best to use state.matches(...)
.
We can do this with if/else if/else
blocks:
// ...
if (state.matches('idle')) {
return /* ... */;
} else if (state.matches({ loading: 'user' })) {
return /* ... */;
} else if (state.matches({ loading: 'friends' })) {
return /* ... */;
} else {
return null;
}
We can also continue to use switch
, but we must make an adjustment to our approach. By setting the expression of the switch
to true
, we can use state.matches(...)
as a predicate in each case
:
switch (true) {
case state.matches('idle'):
return /* ... */;
case state.matches({ loading: 'user' }):
return /* ... */;
case state.matches({ loading: 'friends' }):
return /* ... */;
default:
return null;
}
A ternary statement can also be considered, especially within rendered JSX:
const Loader = () => {
const [state, send] = useMachine(/* ... */);
return (
<div>
{state.matches('idle') ? (
<Loader.Idle />
) : state.matches({ loading: 'user' }) ? (
<Loader.LoadingUser />
) : state.matches({ loading: 'friends' }) ? (
<Loader.LoadingFriends />
) : null}
</div>
);
};
You can persist and rehydrate state with useMachine(...)
via options.state
:
// ...
// Get the persisted state config object from somewhere, e.g. localStorage
const persistedState = JSON.parse(localStorage.getItem('some-persisted-state-key'));
const App = () => {
const [state, send] = useMachine(someMachine, {
state: persistedState // provide persisted state config object here
});
// state will initially be that persisted state, not the machine's initialState
return (/* ... */)
}
The service
created in useMachine(machine)
can be referenced as the third returned value:
// vvvvvvv
const [state, send, service] = useMachine(someMachine);
You can subscribe to that service's state changes with the useEffect
hook:
// ...
useEffect(() => {
const subscription = service.subscribe((state) => {
// simple state logging
console.log(state);
});
return subscription.unsubscribe;
}, [service]); // note: service should never change
FAQs
XState tools for React
The npm package @xstate/react receives a total of 535,986 weekly downloads. As such, @xstate/react popularity was classified as popular.
We found that @xstate/react demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 3 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Research
Security News
Socket’s threat research team has detected six malicious npm packages typosquatting popular libraries to insert SSH backdoors.
Security News
MITRE's 2024 CWE Top 25 highlights critical software vulnerabilities like XSS, SQL Injection, and CSRF, reflecting shifts due to a refined ranking methodology.
Security News
In this segment of the Risky Business podcast, Feross Aboukhadijeh and Patrick Gray discuss the challenges of tracking malware discovered in open source softare.