We use all the concepts we learned in the previous modules to implement the searching logic with XState.
In practice, we are going to implement the following:
- Actor that performs async request to fetch posts
- State machine (actor) responsible to manage the current state of the app ("searching" state and post list)
These two actors are implemented in isolation. The state machine is then going to invoke the fetch posts actor and wait for the request to complete.
This is how the final model looks like:
First we add Context
and Event
in types
together with the initialContext
:
import { setup } from "xstate";
import { initialContext, type Context } from "./shared";
type Event =
| { type: "update-query"; value: string }
| { type: "submit-search" };
const machine = setup({
types: {
events: {} as Event,
context: {} as Context,
},
}).createMachine({
context: initialContext,
});
Since we perform a search request as soon as the component loads, we define initial
as "Searching":
const machine = setup({
types: {
events: {} as Event,
context: {} as Context,
},
}).createMachine({
context: initialContext,
initial: "Searching",
states: {
Searching: {}
}
});
Invoking actor in initial state
An async request is implemented inside an actor with fromPromise
as we did in the previous module. The actor requires as input the query to search for:
import { fromPromise } from "xstate";
const searchingActor = fromPromise(
async ({ input }: { input: { query: string } }): Promise<Post[]> =>
searchRequest(input.query)
);
We add the searchingActor
inside setup/actors
:
const machine = setup({
types: {
events: {} as Event,
context: {} as Context,
},
actors: { searchingActor },
}).createMachine({
context: initialContext,
initial: "Searching",
states: {
Searching: {}
}
});
We can now invoke the searchingActor
inside the "Searching" state passing the query as input from context
const machine = setup({
types: {
events: {} as Event,
context: {} as Context,
},
actors: { searchingActor },
}).createMachine({
context: initialContext,
initial: "Searching",
states: {
Searching: {
invoke: {
src: "searchingActor",
input: ({ context }) => ({ query: context.query })
}
}
}
});
The key here is that "Searching" is the initial state, and the invoke
action is executed when the state is entered.
Therefore, adding invoke
in the initial state allows to perform a request "on mount".
This behaves the same as
useEffect
with an empty dependency array, but without all the drawbacks ofuseEffect
.