Skip to content

Instantly share code, notes, and snippets.

@ChrisShank
Created October 19, 2021 00:32
Show Gist options
  • Save ChrisShank/369aa8cbd4002244d7769bd1ba3e232a to your computer and use it in GitHub Desktop.
Save ChrisShank/369aa8cbd4002244d7769bd1ba3e232a to your computer and use it in GitHub Desktop.
My *current* approach to handling routing with xstate
/**
This gist is more or less pseudocode for how I think we should deal with routing.
The largest problem with routing is that is permates *all* layers of an application (business logic, rendering, ect.)
and most routing library to be an all in one solution to this problem. As a result routing libraries are becoming more like
state management libraries than routing libraries. I recommend that we break routing into three distinct concerns:
1. Application-specific routing logic
- Use any state management tool you want, xstate is a good fit here
2. Serializing/deserializing the URL
- Deals specifically with interacting with the broswers History API and extracting data from the URL
- `navaid` (https://github.com/lukeed/navaid) is decent solution.
- Building out my own solution called `xrouter` (https://github.com/ChrisShank/xrouter). [WIP]
3. Mapping the current state to a set of components
A working POC with navaid can be found here: https://codesandbox.io/s/routing-with-xstate-poc-gm9km?file=/src/index.ts
*/
const todoAppMachine = createMachine(
{
id: 'todoApp',
invoke: { src: 'router' },
/**
1. Your application specific routing logic goes here. This machine orchestrates multiple sources or truth together (e.g. URL, authentication, ect.).
Listen to events sent from the router and send events to the router to keep the URL *eventually consistent* with your URL.
*/
},
{
services: {
// 2. An actor that represents the browser's router.
router: () => (sendBack, receive) => {
const router = Navaid("/", () => sendBack("routeNotFound"));
// Deserialize events from the URL
router
.on("/", () => sendBack("navigateToTodos"))
.on("/todos", () => sendBack("navigateToTodos"))
.on("/todo/new", () => sendBack("navigateToNewTodo"))
.on("/todo/:id", (params) =>
sendBack(todosModel.events.navigateToTodo(Number(params.id)))
);
// Serialize current state to URL
receive((event) => {
if (event.type === "pushTodos") {
router.route("/todos");
} else if (event.type === "pushTodo") {
router.route(`/todo/${event.id}`);
} else if (event.type === "pushNewTodo") {
router.route(`/todo/new`);
}
});
router.listen();
return () => router.unlisten();
}
}
)
// 3. Components are not tied to a specific URL they are tied to a state you are in.
service.onTransition((state) => {
if (state.hasTag("todos")) {
render(todos(state.context.todos));
} else if (state.hasTag("new-todo")) {
render(newTodo());
} else if (state.hasTag("todo") && state.context.selectedTodo) {
render(todo(state.context.selectedTodo));
} else if (state.hasTag("invalid-todo")) {
render(invalidTodo());
} else if (state.hasTag("not-found")) {
render(notFound());
}
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment