Encodera

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.

Оглавление

Формы: отправка данных на сервер

Чтобы добавить статью, страница должна отправить данные формы в API. Данные формы отправляются по действию пользователя, поэтому здесь нужен не useFetch, а $fetch с методом POST. Сервер для этого уже готов: обработчик server/api/posts.post.ts из главы о серверной части.

Страница новой статьи

npx nuxi add page posts/new

Замените содержимое app/pages/posts/new.vue:

<!-- app/pages/posts/new.vue -->
<script setup lang="ts">
const form = reactive({ title: '', text: '' })
 
async function submit() {
  const post = await $fetch('/api/posts', {
    method: 'POST',
    body: form
  })
  await navigateTo(`/posts/${post.id}`)
}
</script>
 
<template>
  <form @submit.prevent="submit">
    <h2>Новая статья</h2>
    <input v-model="form.title" placeholder="Заголовок">
    <textarea v-model="form.text" placeholder="Текст" />
    <button>Опубликовать</button>
  </form>
</template>

$fetch отправляет объект form в теле запроса в формате JSON. Сервер возвращает созданную статью, и navigateTo открывает её страницу. Заполните форму на /posts/new, нажмите «Опубликовать» — откроется новая статья. Добавьте в меню макета ссылку <NuxtLink to="/posts/new">, чтобы попадать на форму.

Ошибки и состояние отправки

Сервер отвечает кодом 400, если поле пустое. $fetch в этом случае бросает ошибку, а текст ответа лежит в её поле data:

<!-- app/pages/posts/new.vue -->
<script setup lang="ts">
const form = reactive({ title: '', text: '' })
const errorMessage = ref('')
const sending = ref(false)
 
async function submit() {
  sending.value = true
  errorMessage.value = ''
  try {
    const post = await $fetch('/api/posts', { method: 'POST', body: form })
    await navigateTo(`/posts/${post.id}`)
  } catch (e) {
    const err = e as { data?: { message?: string } }
    errorMessage.value = err.data?.message ?? 'Не удалось сохранить статью'
  } finally {
    sending.value = false
  }
}
</script>
 
<template>
  <form @submit.prevent="submit">
    <input v-model="form.title" placeholder="Заголовок">
    <textarea v-model="form.text" placeholder="Текст" />
    <p v-if="errorMessage">{{ errorMessage }}</p>
    <button :disabled="sending">Опубликовать</button>
  </form>
</template>

Отправьте форму с пустым текстом — под полями появится «Заполните заголовок и текст» от сервера. Пока запрос идёт, кнопка выключена, и статья не отправится дважды.

Форма комментария

Создайте компонент app/components/post/CommentForm.vue:

<!-- app/components/post/CommentForm.vue -->
<script setup lang="ts">
const props = defineProps<{ postId: number }>()
const emit = defineEmits<{ added: [] }>()
 
const author = ref('')
const text = ref('')
 
async function send() {
  await $fetch(`/api/posts/${props.postId}/comments`, {
    method: 'POST',
    body: { author: author.value, text: text.value }
  })
  text.value = ''
  emit('added')
}
</script>
 
<template>
  <form @submit.prevent="send">
    <input v-model="author" placeholder="Ваше имя">
    <textarea v-model="text" placeholder="Комментарий" />
    <button>Отправить</button>
  </form>
</template>

После отправки компонент сообщает родителю событием added. На странице статьи, где данные загружает useAsyncData, подключите форму и обновление:

<!-- app/pages/posts/[id].vue -->
<PostCommentForm v-if="data" :post-id="data.post.id" @added="refresh()" />

refresh возьмите из результата: const { data, refresh } = await useAsyncData(...). Новый комментарий появится под статьёй сразу после отправки.

Частая ошибка

Вызвать useFetch внутри обработчика submit. Composables вроде useFetch предназначены для <script setup>: внутри обработчика события они работают ненадёжно и выводят предупреждение. Для запросов по нажатию кнопки или отправке формы используйте $fetch.

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.