State Store
The great thing about Markup state is the fact that it is a standalone API that works great witht the template itself.
What this allows you to do is manage shared/global state away from the component and inject them directly into the template or perform side effects where needed. All that without worrying about subcriptions and cleanups.
Create a state store
Take for example this todos state store:
1// src/stores/todos.ts2 3type UUID = `${string}-${string}-${string}-${string}-${string}`4 5export interface Todo {6 id: UUID7 name: string8 description: string9 status: 'done' | 'pending' | 'removed'10 dateCreated: Date11 dateLastUpdated: Date12}13 14const [todos, updateTodos] = state<Todo[]>([])15 16export const todoList = todosAs you can see, we can create a file dedicated to manage a particular state we want to use in multiple places in our application.
Consuming the store state
We don't have to worry about subscription when it comes to rendering this data, we can inject it directly into the template.
1import {todoList} from "./stores/todos"2 3const App = () => {4 5 const renderTodo = () => {...}6 7 // html will handle all subscribing and8 // unsubscribing from todoList state9 return html`10 <ul id="todos">11 ${repeat(todoList, renderTodo)}12 </ul>13 `14}Easy enough, to perform something whenever this list changes, we can just use the effect.
1import { todoList } from './stores/todos'2 3const defaultTodosByStatus = { done: [], pending: [], removed: [] }4 5const [todosByStatus, updateTodosByStatus] = state(defaultTodosByStatus)6 7// effect will handle subscribing to the todoList state8const unsubFromEffect = effect(() => {9 updateTodosByStatus(10 todoList().reduce((acc, todo) => {11 if (!acc[todo.status]) {12 acc[todo.status] = []13 }14 15 acc[todo.status].push(todo)16 17 return acc18 }, defaultTodosByStatus)19 )20})21 22// call "unsubFromEffect" to unsubscribe from todoList stateDefine store actions
Because the store is simply a file, we can expose functions that perform changes in the data without exposing the logic or complexity of the state.
1// src/stores/todos.ts2 3...4 5export const createTodo = (name: string) => {6 const dateCreated = new Date();7 const todo: Todo = {8 id: crypto.randomUUID(),9 name,10 description: "",11 status: "pending",12 dateCreated,13 dateLastUpdated: dateCreated14 }15 16 updateTodos(prev => [...prev, todo])17}18 19export const updateTodo = (id: UUID, data: Partial<Todo>) => {20 updateTodos(prev => prev.map(todo => {21 if(todo.id === id) {22 return {23 ...todo,24 name: data.name ?? todo.name,25 description: data.description ?? todo.description,26 status: data.status ?? todo.status,27 dateLastUpdated: new Date()28 }29 }30 31 return todo;32 }))33}34 35export const deleteTodo = (id: UUID) => {36 updateTodos(prev => prev.filter(todo => {37 return todo.id !== id;38 }))39}40 41export const clearTodos = () => {42 updateTodos([])43}These actions can be whatever you want. They can
- store data in
localstorageorindexedDB; - be asynchronous;
- call servers APIs to save data;
- perform validations;
- map the data;
- etc
Data storage and state management does not have to be complex and all you need from here is use the APIs readily available in the environment to do whatever you want.
Look at this example of todo state store with localstorage.
edit this doc