Created
February 19, 2025 16:12
-
-
Save ataylorme/7f4a962351c9ad9f3526b64b4b7c3407 to your computer and use it in GitHub Desktop.
XState Chuck Norris Joke Machine
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { assign, fromPromise, setup } from "xstate"; | |
async function getChuckNorrisJoke(category?: string): Promise<Joke> { | |
let url = "https://api.chucknorris.io/jokes/random"; | |
if (category !== undefined && category !== "") { | |
url += `?category=${category}`; | |
} | |
const response = await fetch(url); | |
const data = await response.json(); | |
return { | |
id: data.id, | |
value: data.value, | |
} as Joke; | |
} | |
async function getChuckNorrisJokeCategories(): Promise<string[]> { | |
const url = "https://api.chucknorris.io/jokes/categories"; | |
const response = await fetch(url); | |
const data = await response.json(); | |
return data; | |
} | |
interface Joke { | |
id: string; | |
value: string; | |
} | |
interface Context { | |
jokes: Set<Joke>; | |
errors: string[]; | |
numJokes: number; | |
category?: string; | |
categories: Set<string>; | |
} | |
const Context: Context = { | |
jokes: new Set<Joke>(), | |
errors: [], | |
numJokes: 5, | |
categories: new Set<string>(), | |
}; | |
interface GET_JOKES { | |
readonly type: "GET_JOKES"; | |
readonly numJokes: Context["numJokes"]; | |
} | |
interface CLEAR_JOKES { | |
readonly type: "CLEAR_JOKES"; | |
} | |
interface CLEAR_ERRORS { | |
readonly type: "CLEAR_ERRORS"; | |
} | |
interface GET_CATEGORIES { | |
readonly type: "GET_CATEGORIES"; | |
} | |
interface SET_CATEGORY { | |
readonly type: "SET_CATEGORY"; | |
readonly category: string; | |
} | |
interface SET_NUM_JOKES { | |
readonly type: "SET_NUM_JOKES"; | |
readonly numJokes: number; | |
} | |
type Events = | |
| GET_JOKES | |
| CLEAR_JOKES | |
| CLEAR_ERRORS | |
| GET_CATEGORIES | |
| SET_CATEGORY | |
| SET_NUM_JOKES; | |
const onGetJoke = async (context: Context): Promise<Partial<Context>> => { | |
const { category, jokes } = context; | |
const joke = await getChuckNorrisJoke(category); | |
return { | |
jokes: jokes.add(joke), | |
}; | |
}; | |
const onGetCategories = async (): Promise<Partial<Context>> => { | |
const newCategories = await getChuckNorrisJokeCategories(); | |
return { | |
categories: new Set(newCategories), | |
}; | |
}; | |
const chuckNorrisJokeMachine = setup({ | |
types: { | |
context: {} as Context, | |
events: {} as Events, | |
}, | |
actors: { | |
onGetJoke: fromPromise< | |
Partial<Context>, | |
{ params: GET_JOKES; context: Context } | |
>(({ input: { context } }) => onGetJoke(context)), | |
onGetCategories: fromPromise< | |
Partial<Context>, | |
{ params: GET_CATEGORIES; context: Context } | |
>(() => onGetCategories()), | |
}, | |
actions: {}, | |
}).createMachine({ | |
id: "chuckNorrisJokeMachine", | |
context: Context, | |
initial: "Idle", | |
states: { | |
GettingJokes: { | |
id: "gettingJokes", | |
// How to get jokes in parallel | |
// based on the number of jokes | |
// in the context | |
invoke: { | |
src: "onGetJoke", | |
input: ({ context, event }) => { | |
if (event.type === "GET_JOKES") { | |
return { context, params: event }; | |
} | |
throw new Error(`event type ${event.type} cannot call onGetJoke`); | |
}, | |
onDone: { | |
target: "Idle", | |
actions: assign(({ event }) => event.output), | |
}, | |
}, | |
}, | |
GettingCategories: { | |
invoke: { | |
src: "onGetCategories", | |
input: ({ context, event }) => { | |
if (event.type === "GET_CATEGORIES") { | |
return { context, params: event }; | |
} | |
throw new Error( | |
`event type ${event.type} cannot call onGetCategories`, | |
); | |
}, | |
onDone: { | |
target: "Idle", | |
actions: assign(({ event }) => event.output), | |
}, | |
}, | |
}, | |
Idle: { | |
on: { | |
GET_JOKES: { | |
target: "GettingJokes", | |
}, | |
GET_CATEGORIES: { | |
target: "GettingCategories", | |
}, | |
CLEAR_ERRORS: { | |
actions: assign({ errors: [] }), | |
}, | |
CLEAR_JOKES: { | |
actions: assign({ jokes: new Set<Joke>() }), | |
}, | |
SET_NUM_JOKES: { | |
actions: assign({ | |
numJokes: ({ event }) => event.numJokes, | |
}), | |
} | |
}, | |
}, | |
}, | |
}); | |
export default chuckNorrisJokeMachine; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment