javascript – To-do app front-end in Vue 3

I have put together a To-do Application with the Slim framework on the back-end (API) and a Vue 3 front-end.

In the main App.vue file I have:

<template>
  <div id="app">
    <Header 
    title="My todo list" 
    :unsolvedTodos = unsolvedTodos
    />

    <List 
      :todos="todos"
      :dataIsLoaded=dataIsLoaded
      @delete-todo="deleteTodo" 
      @toggle-todo="toggleTodo" />

    <Footer
      :isValidInput=isValidInput
      newTitle = ""
      placeholder= "+ Add new todo"
      validationMsg = "Please add at least 3 characters"
      @add-todo="addTodo"
      />

  </div>
</template>

<script>
import axios from 'axios'
import '@fortawesome/fontawesome-free/js/all.js';
import Header from './components/Header.vue'
import List from './components/List.vue'
import Footer from './components/Footer.vue'

export default {
  name: 'App',
  components: {
    Header,
    List,
    Footer
  },

  data() {
    return {
      apiUrl: "http://todo.com/api",
      dataIsLoaded: false,
      isValidInput: true,
      todos: (),
      unsolvedTodos: (),
    }
  },

  methods: {
    showTodos: function(){
      axios.get(`${this.apiUrl}/todos`)
      .then((response) => {
        this.todos = response.data;
      })
     .then(this.getUnsolvedTodos)
     .then(this.dataIsLoaded = true);
    },

    getUnsolvedTodos: function(){
      this.unsolvedTodos = this.todos.filter(todo => {
        return todo.completed == 0;
      });
    },

    toggleTodo: function(todo) {
      let newStatus = todo.completed == "0" ? 1 : 0;
      axios.put(`${this.apiUrl}/todo/update/${todo.id}`, {
        title: todo.title,
        completed: newStatus
      })
    },

    deleteTodo: function(id) {
      axios.delete(`${this.apiUrl}/todo/delete/${id}`)
    },

    addTodo: function(newTitle){
     const newToDo = {
        title: newTitle,
        completed: 0
      }
     
      if(newTitle.length > 2){
        this.isValidInput = true;
        axios.post(`${this.apiUrl}/todo/add`, newToDo);
      } else {
        this.isValidInput = false;
      }
    }
  },

  created() {
    this.showTodos();
  },

  watch: {
    todos() {
      this.showTodos();
    }
  }
}
</script>

In Header.vue:

<template>
  <header>
    <span class="title">{{title}}</span>
    <span class="count" :class="{zero: unsolvedTodos.length === 0}">{{unsolvedTodos.length}}</span>
  </header>
</template>

<script>
export default {
  props: {
    title: String,
    unsolvedTodos: Array
  },
}
</script>

In Footer.vue:

<template>
  <footer>
    <form @submit.prevent="addTodo()">
      <input type="text" :placeholder="placeholder" v-model="newTitle">
      <span class="error" v-if="!isValidInput">{{validationMsg}}</span>
    </form>
  </footer>
</template>

<script>
export default {
  name: 'Footer',

  props: {
    placeholder: String,
    validationMsg: String,
    isValidInput: Boolean
  },

  data () {
    return {
     newTitle: '',
    }
  },

  methods: {
    addTodo() {
      this.$emit('add-todo', this.newTitle)
      this.newTitle = ''
    }
  }
}
</script>

The to-do list (List.vue):

<template>
  <transition-group name="list" tag="ul" class="todo-list" v-if=dataIsLoaded>
      <TodoItem v-for="(todo, index) in todos"
        :key="todo.id" 
        :class="{done: Boolean(Number(todo.completed)), current: index == 0}" 
        :todo="todo" 
        @delete-todo="$emit('delete-todo', todo.id)"
        @toggle-todo="$emit('toggle-todo', todo)"
        />
    </transition-group>
    <div class="loader" v-else></div>   
</template>

<script>
import TodoItem from "./TodoItem.vue";

export default {
  name: 'List',

  components: {
    TodoItem,
  },

  props: {
    dataIsLoaded: Boolean,
    todos: Array
  },

  emits: (
    'delete-todo',
    'toggle-todo'
  )
}
</script>

The single to-do item (TodoItem.vue):

<template>
  <li>
    <input type="checkbox" :checked="Boolean(Number(todo.completed))" @change="$emit('toggle-todo', todo)" />
    <span class="title">{{todo.title}}</span>
    <button @click="$emit('delete-todo', todo.id)">
      <i class="fas fa-trash-alt"></i>
    </button>
 </li>
</template>

<script>
export default {
  name: 'TodoItem',

  props: {
    todo: Object
  }
}
</script>

Questions / concerns:

  1. Is the application well-structured?
  2. Could the code be significantly “shortend”?