Zerg Zerg
Zerg Zerg

Reputation: 53

Nuxt 3 [Vue warn]: Hydration children mismatch in <div>: server rendered element contains more child nodes than client vdom

I'm using Nuxt 3 + Pinia + FirebaseStore in my project and when I wrap my load data functions from Firebese by the AsyncData function I started getting an error "Hydration children mismatch in ". I didn't manage to find a good example of the nuxt 3 app with ssr or a solution for how to fix this warning or good example of nuxt 3 app with ssr or a solution how to fix this.

In Layout component, I'm getting the error from Footer. enter image description here

And in the page section from ArticleLisctBlock component. enter image description here enter image description here

Layout default component:

<script setup lang="ts">
import { useAuthStore } from "@/stores/authStore";
import { storeToRefs } from "pinia";


//Get  Authorized user
const { isAuthorized } = useAuthStore();
const { statAuth } = storeToRefs(useAuthStore());

//Fetch AboutUs  data
const { aboutUs } = storeToRefs(useAboutUsStore());
const { getAboutUs } = useAboutUsStore();

//Fetch Contactsr links data
const { stateData } = storeToRefs(useContactStore());
const { getContacts } = useContactStore();

//Fetch Nav and Footer links data
const { navLinks, footerLinks } = storeToRefs(useNavStorage());
const { getList, getFooterList } = useNavStorage();

//Fetch Articles data
const { postsState } = storeToRefs(useArticleStore());
const { getPostList } = useArticleStore();

const { imageList } = storeToRefs(useGalaryStore());
const { getGalaryDBList } = useGalaryStore();

//CategoryLinks
const { categoryState } = storeToRefs(useCategoryStorage());
const { getGategoryList } = useCategoryStorage();

//Fetch Podcast List
const { podcastsState } = storeToRefs(usePodcastsStore());
const { getPodCastList } = usePodcastsStore();

//Fetch Advertisement List
const { advertiseList } = storeToRefs(useAdvertiseStore());
const { getAdvertiseList } = useAdvertiseStore();

async function loadStores2() {
  await getPostList();
  await getFooterList();
  await getPodCastList();
  await getList();
  await getGategoryList();
  await getGalaryDBList();
  await getAdvertiseList();
  await getAboutUs();
  await getContacts();
}
await useAsyncData("loadData", loadStores2);

onMounted(async () => {
//check auth user in firebase
  await isAuthorized();
});
</script>

<template>
  <div class="defaultLayout">
    <div class="header_block">
      <UiAddTopHeader v-if="footerLinks" :category-links="navLinks" :top-links="footerLinks" />
      <UiAddHeaderMiddle />
      <UiAddHeader v-if="navLinks" :nav-links="navLinks" />
    </div>

    <div class="body_block">
      <slot />
    </div>
    <div class="footer_block">
      <UiAddFooter
        v-if="footerLinks?.length && postsState?.postList?.length"
        :about-us-links="footerLinks"
        :favorites="postsState?.favoriteList"
        :categories="categoryState" />
    </div>
  </div>
</template>

Footer component.

<script setup lang="ts">
import type { IArticle } from "types/IArticle";
import type { ICategory } from "types/ICategory";
 
import type { INavigation } from "types/INavigation";
 
defineProps({
  categories: {
    type: Array as PropType<ICategory[]>,
    default: null,
  },
  aboutUsLinks: {
    type: Array as PropType<INavigation[]>,
    default: null,
  },
  favorites: {
    type: Array as PropType<IArticle[]>,
    default: null,
  },
});
 
</script>
 
<template>
  <div class="footer_container">
    <img class="footer_image" src="/images/footer.jpg" alt="footer" />
    <div class="content">
      <div class="media_block grid_block">
        <h2 class="media_block_title">WORLD IMPULSE</h2>
        <p>© Munich, LLC. All rights reserved, LLC</p>
        <UiAddSocialMediaList :is-inline-block="true" />
      </div>
      <div class="about_block grid_block">
        <h2 class="title_block">About Us</h2>
        <UiAddAboutLinks
          v-if="aboutUsLinks"
          direction="flex"
          gaps="20px"
          :aboutlinks="aboutUsLinks" />
      </div>
      <div class="popular_block grid_block">
        <h2 class="title_block">Popular Category</h2>
        <UiAddAboutLinks
          v-if="categories?.length"
          direction="grid"
          gaps="20px"
          :categorylinks="categories?.slice(1, 8)" />
      </div>
      <div class="editor_picks grid_block">
        <h2 class="title_block">Editor Piks</h2>
 
        <ArticleSingleArticle
          class-type="image_content_rigth"
          :show-title="true"
          :show-image="true"
          font-size="16px"
          :font-weight="400"
          v-for="(el, i) in favorites.slice(0, 2)"
          :key="i"
          :single-post="el" />
      </div>
    </div>
  </div>
</template>

[Id],page where ArticleListBlock component calls

<script setup lang="ts">
import { storeToRefs } from "pinia";
import type { IArticle } from "types/IArticle";
 
//Get Route params
const route = useRoute();
const routeSlug = String(route.params.slug);
const routeId = String(route.params.id);
 
//Fetch Articles data
const { postsState } = storeToRefs(useArticleStore());
const { isExistPost } = useArticleStore();
 
//Get Category data
const categoryStore = useCategoryStorage();
const { isExistCategory } = categoryStore;
 
// Check route params
const isRouteCorrect = () => {
  if (routeSlug === "search") {
    return true;
  } else if (routeId && (routeId === "list" || isExistPost(routeId))) {
    return true;
  }
 
  return false;
};
 
//Throw  an error id route params are not  correct
if (!isRouteCorrect()) {
  throw createError({ statusCode: 404, statusMessage: `The Page not found` });
}
</script>
 
<template>
  <div class="wrapper">
    <ArticleListBlock v-if="route.params.slug === 'search' || (routeId && routeId === 'list')" />
    <ArticleSingleBlock v-else-if="routeId && isExistPost(routeId)" />
  </div>
</template>

ArticleListBlock component

<script setup lang="ts">
import type { NuxtError } from "nuxt/app";
import { storeToRefs } from "pinia";
import type { IArticle } from "types/IArticle";
 
const route = useRoute();
const articlesByCategory = ref<IArticle[]>([]);
const search = ref("");
const errorResponse = ref<NuxtError>();
 
// Articles state
const { postsState } = storeToRefs(useArticleStore());
const { getArticlesByCategory, findArticlesByName } = useArticleStore();
 
// Advertisement galary state
const { advertiseList } = storeToRefs(useAdvertiseStore());
 
if (route.params.slug === "search") {
  search.value = String(route.params.id);
 
  console.log(search.value);
}
 
// Getting data by search request
const searchComputed = computed(() => {
  if (!search.value) {
    route.params.slug === "search" && (articlesByCategory.value = postsState.postList);
    route.params.id === "list" &&
      (articlesByCategory.value = getArticlesByCategory(String(route.params.slug)));
 

    return articlesByCategory.value;
  } else {
    articlesByCategory.value = findArticlesByName(search.value);
 
    if (!articlesByCategory.value.length) {
      errorResponse.value = createError({ statusCode: 404, statusMessage: "Not found results" });
    }
    return articlesByCategory.value;
  }
});
 
useHead({
  title: `${String(route.params.slug)} list`,
  meta: [
    { name: `${String(route.params.slug)}`, content: `${String(route.params.slug)} daily news` },
  ],
});
</script>
 
<template>
  <div class="list_container grid_block">
    <div class="top grid_block">
      <div class="advertise_block">
        <UiElementsAdvertise label="Advertisement" :link="advertiseList.databaseList[1]" />
      </div>
 
      <div class="topic grid_block">
        <h5 class="category">CATEGORY</h5>
        <h1 class="title_category">{{ String(route?.params?.slug)?.toLocaleUpperCase() }}</h1>
 
        <h4 class="info">
          Read the latest news with the Best WordPress News Theme – Newspaper by Sergio Belov!
        </h4>
      </div>
      <div class="form_block">
        <UiElementsAddPostInput
          label="Search"
          width-form="100%"
          font-size="2rem"
          name="search"
          placeholder="Input search"
          v-model:value.trim="search" />
      </div>
    </div>
 
    <div class="main_section">
      <div class="list grid_block" v-if="searchComputed.length">
        <div class="item" v-for="(el, i) in searchComputed" :key="i">
          <ArticleSingleArticle
            class-type="image_content_left"
            :show-image="true"
            :show-title="true"
            :show-short-body="true"
            :show-date="true"
            :single-post="el"
            :id="el?.id" />
        </div>
      </div>
 
      <ErrorResponse v-else-if="errorResponse" :error-event="errorResponse" />
 
      <div class="right_bar" v-if="postsState.postList.length">
        <h3>Latest News</h3>
        <ArticleSingleArticle
          class-type="image_content_rigth"
          :show-title="true"
          :show-image="true"
          :show-date="true"
          v-for="(el, i) in postsState.postList?.slice(4, 7)"
          :key="i"
          :single-post="el" />
      </div>
    </div>
  </div>
</template>

ArticleSingleArticle component from with calls from ArticleListBlock

<script setup lang="ts">
import type { IArticle } from "types/IArticle";

defineProps({
  singlePost: {
    type: Object as PropType<IArticle>,
    default: null,
  },
  fontSize: {
    type: String,
    default: "18px",
  },
  fontWeight: {
    type: Number,
    default: 600,
  },
  rowSize: {
    type: String,
    default: "auto",
  },
  showContent: {
    type: Boolean,
    default: false,
  },
  showShortBody: {
    type: Boolean,
    default: false,
  },
  showCategory: {
    type: Boolean,
    default: false,
  },
  showTitle: {
    type: Boolean,
    default: false,
  },
  showImage: {
    type: Boolean,
    default: false,
  },
  showDate: {
    type: Boolean,
    default: false,
  },
  classType: {
    type: String,
    default: "",
  },
});
</script>

<template>
  <div class="single_post" :class="classType">
    <div class="tag" v-if="showCategory">
      <h5>{{ singlePost?.category?.toLocaleUpperCase() }}</h5>
    </div>
    <div class="title_post" v-if="showTitle">
      <NuxtLink :to="{ path: `/${singlePost?.category}/${singlePost?.id}` }">
        <p>{{ singlePost?.title }}</p>
      </NuxtLink>
    </div>

    <div class="preview_image" v-if="showImage">
      <img class="image" :src="singlePost?.image" alt="postPriview" />
    </div>

    <div v-if="showShortBody" class="shortn_block">
      <p>{{ singlePost?.shortBody }}</p>
    </div>
    <div class="post_created" v-if="showDate">
      <span>
        By <b class="author">{{ singlePost?.author }}</b>
        {{ singlePost?.date && formatDate(singlePost?.date) }}
      </span>
    </div>

    <div v-if="showContent" class="description_block">
      <div class="pre" v-html="singlePost?.body"></div>
      <div class="share_block_container">
        <UiElementsShareElement />
        <UiAddSocialMediaList :is-inline-block="true" :enable-bg="true" color-icon="white" />
      </div>
    </div>
  </div>
</template>

I remove all comment tag in every section and components and try to remove v-if where I could but the error didn't disappeared. The tag ClientOnly don't fit me , because I want that my data render on server.

Upvotes: 2

Views: 6554

Answers (3)

Evgeniia K
Evgeniia K

Reputation: 11

In my case I've used v-html with variable, which contained a paragraph inside. For example, myVHtmlVariable="<p>sometext</p>". And then I inserted it into the paragraph like this: <p v-html="myVHtmlVariable" />. I replaced the paragraph with a div: <div v-html="myVHtmlVariable" />, and this fixed the error.

Upvotes: 1

Zerg Zerg
Zerg Zerg

Reputation: 53

Thanks for your answer Rodrigo. But it isn't the one way to avoid this warning. I've noticed this after creating a few project on Nuxt 3. I catched this warning when I used a table tag and reactive variables inside of the tables but when I rewrote same logic into a div tags the warning disappeared . Also when I forgot the rule that the template must have the only one node and used wrong v-if logic to display blocks like that :

 <template>
      <div v-if="someVar === "something">
        <p>some text in here</p>       
      </div>
  <div v-if="someVar === "otherSomething">
        <p>some text in here</p>       
      </div>
    <div v-else>
        <p>some text in here</p>       
      </div>
    </template>

So the code above is also leads to the warning. Instead of it, it's necessary to use v-if, v-else-if, v-else structure that the only one block was accessed during the render. And also wrong logic using v-if inside of a one node like that :

<template>
<div v-if="someVar">
      <div v-if="someVar === "something">
            <p>some text in here</p>       
          </div>
      <div v-if="someVar === "otherSomething">
            <p>some text in here</p>       
          </div>
        <div v-else>
            <p>some text in here</p>       
          </div>
 </div>
</template>

So in this case to avoid the warning it needs to use the right logic v-if, v-else-if, v-else.

Upvotes: 0

Rodrigo Rubio
Rodrigo Rubio

Reputation: 1760

For anyone facing the issue where you're required to use html tags within your component e.g. p, href etc.. tags. Use "ClientOnly" to wrap those elements.

For example -

<ClientOnly>
  <div>
    <p>some text in here</p>
    <a href="<my-link>">Click Me</a>
  </div>
</ClientOnly>

Upvotes: 2

Related Questions