DEV Community

loading...
Cover image for Todo App with Xstate and Vue composition API

Todo App with Xstate and Vue composition API

Jasmin Virdi
Just another Techie! :)
・3 min read

In the last post, I have briefly discussed the use of State Machines and their advantages in Frontend Applications. Xstate library has made it easier in leveraging State Machines in our Frontend Applications.

I wanted to try the Xstate library so I thought of recreating my simple Todo App which was built using Vue 2. This exercise has helped me in learning about State Machines use in real applications and new features of Vue 3 which is the composition API 🀩.

Project Walkthrough

I used the package @vue/composition-api as I was planning to recreate an existing Todo Application.

The Todo application is primarily based on the Actor Model where a new machine actor is created by reference with a unique identity and the parent machine is automatically subscribed to the spawned child machine's state.

fetchList: assign({
    todoList: (context, event) => {
        return context.todoList.map((todo) => ({
            ...todo,
            ref: spawn(todoItemMachine.withContext(todo))
        }))
    }
})
Enter fullscreen mode Exit fullscreen mode

This action on the state change of todoMachine will spawn a new actor of todoItemMachine which will be scoped to the service where it is spawned.

The classification of machines was based on the idea that actions related to TodoList like fetch, edit, delete and create will be in one place and the action to mark the todo as completed will be in one place πŸ˜‰

The todoItemMachine is defined with only two actions that are completed and pending.

const todoItemMachine = Machine({
    id: 'todoItem',
    initial: 'completed',
    states: {
        completed: {
            on: {
                completeTask: {
                    actions: 'completedTodoAction'
                }
            }
        },
        pending: {}
    },
    actions: {
        completedTodoAction: assign((context, event) => {})
    }
})
Enter fullscreen mode Exit fullscreen mode

With this, our state logic is ready to be integrated so let's start with integration πŸ‘©πŸ»β€πŸ’»

I have defined a separate function to handle all the state machine actions in one place so that they can be easily shared across multiple components. Vue composition API has made it possible to keep all the logic related to one entity in one place in the form of functions instead of being separated by optional properties like the earlier versions of Vue.

import { todoMachine } from './index'
import { useMachine } from '@xstate/vue'
import { reactive, computed } from '@vue/composition-api'
import { store } from '../store/todoActions'

export default function stateMachineActions() {
    const {
        state,
        send
    } = useMachine(todoMachine)

    let todoActionStore = reactive({
        store
    })

    function setCurrentState(state) {
        store.commit('setState', state)
    }

    function stateTransitions(action, payload) {
        send({
            type: action,
            payload
        })
    }

    return {
        state,
        stateTransitions,
        todoActionStore,
        setCurrentState,
        todoList: computed(() => state.value.context.todoList)
    }
}
Enter fullscreen mode Exit fullscreen mode

The current state of the machine is saved in the store using the setCurrentState function.

The stateMachineActions function is included in the components and used whenever we want to dispatch the event and transition state from present to next.

import stateMachineActions from './xstate-todo/generateTodoStateMachine'
export default {
    name: 'app',
    /*
     */
    setup(props, context) {
        let {
            state,
            stateTransitions,
            todoActionStore,
            setCurrentState,
            todoList
        } = stateMachineActions()


        function completeTodoItem(todoItem) {
            setCurrentState('editTodoItem')
            stateTransitions('editItem', todoItem)
        }

        return {
            state,
            createTodo,
            todoActionStore,
            todoList,
            deleteTodoItem,
            completeTodoItem
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

In order to observe any changes in the spawned actor due to state changes in the parent, machines listen to useService hook. In this case, if any change occurs in the todo item we can observe the todoItemMachine actor and update the UI accordingly.

setup(props, context) {
        let {
            state,
            send
        } = useService(props.todo.ref)

        /*
         */
        return {
            todoItem: computed(() => state.value.context),
            isEditing,
            completeTodo,
            deleteTodo,
        }
Enter fullscreen mode Exit fullscreen mode

ActorRef is placed within the context of the parent Machine.

You find the complete code on Github and CodeSandbox

Conclusion

This is was great fun and learning exercise. I really enjoyed exploring the library and the new Vue composition API. The official documentation along the examples helped me a lot to complete this exercise. You can refer the following documentation for more details πŸ“

Discussion (0)