Search code examples
vue.jssidebar

Vue blog change rendered post when clicked on a link from "latest posts" sidebar


I've got a problem with my hobby project, I created a BlogPost.vue in which I render the previously clicked post from a Blog.vue page.

In this BlogPost, I render the clicked post's title, content etc, and on the sidebar, I show a small area in which I render 3 of the latest posts from this same category - the posts from Blog.vue.

When I click on the sidebar's links, any of the 3, the browser does change the slug, and the page does re-render itself, except it re-renders the same post that was clicked. If I refresh the page in the browser (or Ctrl+R or F5 etc), then it does render the correct content clicked from this sidebar.

I have no clue why it does that, I can only suppose that I should be watching the route change then somehow refresh what it renders, but no idea as to how.

Blog.vue, this works great, renders the single post clicked

    <script setup lang="ts">
import axios from "axios";
import { ref } from "vue";
import { onMounted } from "vue";
import moment from "moment";

const postsUrl = "http://localhost/wordpress/wp-json/wp/v2/posts";
const posts = ref([] as any);

const isLoading = ref(false);
const errorCaught = ref(false);

var queryOptions = {
  _embed: true,
};

const getPosts = () => {
  isLoading.value = true;

  axios
    .get(postsUrl, { params: queryOptions })
    .then((response) => {
      posts.value = response.data;
      console.log(posts.value);
      isLoading.value = false;
    })
    .then(() => {
      console.log(isLoading.value);
    })
    .catch((error) => {
      if (error) {
        isLoading.value = false;
        errorCaught.value = true;
      }
    });
};

onMounted(async () => {
  getPosts();
});
</script>

<template>
  <transition name="fadeLoading">
    <div v-if="isLoading" class="posts-loading">
      <div class="circle"></div>
    </div>
  </transition>
  <transition name="fadeLoading">
    <div class="errorCaught" v-if="errorCaught">
      There was an error loading news
    </div>
  </transition>
  <div class="blog-container">
    <div class="wrapper">
      <transition-group name="fadeBlog">
        <ul v-if="!isLoading" class="blog-posts-ul" v-for="post in posts">
          <div class="posts-card">
            <a
              ><router-link
                :to="/blog/ + post.slug"
                key="post.id"
                class="posts-permalink"
              >
              </router-link
            ></a>
            <img
              v-if="post.featured_media != 0"
              class="posts-featuredimage"
              :src="post._embedded['wp:featuredmedia'][0].source_url"
              :alt="post.title.rendered"
            />
            <img v-else src="@/assets/logos/favicon-big.png" />
            <div class="posts-date">
              <p>
                {{ moment(post.date).fromNow() + " " + "ago" }}
              </p>
            </div>

            <div class="posts-text">
              <h1 class="posts-title">{{ post.title.rendered }}</h1>

              <p v-html="post.excerpt.rendered" class="posts-excerpt"></p>
            </div>
          </div>
        </ul>
      </transition-group>
    </div>
  </div>
</template>

BlogPost.vue, renders the previously clicked one, but does not show the sidebar's clicked content

<script setup lang="ts">
import { ref, watch, onMounted } from "vue";
import { useRoute } from "vue-router";
import axios from "axios";
import moment from "moment";

const route = useRoute();

const postsUrl = "http://localhost/wordpress/wp-json/wp/v2/posts";
const queryOptions = {
  slug: route.params.blogSlug,
  _embed: true,
};
const post = ref([] as any);
const isLoading = ref(false);

const latestPostsAPI = "http://localhost/wordpress/wp-json/wp/v2/posts";
const latestPosts = ref([] as any);

const errorCaughtLatest = ref(false);

var queryOptionsLatest = {
  _embed: true,
  per_page:3,
};

const getLatest = () => {
  axios
    .get(latestPostsAPI, { params: queryOptionsLatest })
    .then((response) => {
      latestPosts.value = response.data;
      console.log(latestPosts.value);
    })
    .catch((error) => {
      if (error) {
        errorCaughtLatest.value = true;
      }
    });
};

const getPost = () => {
  isLoading.value = true;
  axios
    .get(postsUrl, { params: queryOptions })
    .then((response) => {
      post.value = response.data;
      console.log("Pages retrieved!");
    })
    .catch((error) => {
      console.log(error);
    })
    .then(() => {
      isLoading.value = false;
    });
};

getLatest();
getPost();

</script>

<template>
  <div v-if="!isLoading" class="post-wrapper">
    <div class="wrapper">
      <div class="post-title">{{ post[0].title.rendered }}</div>
      <div class="post-date">
        {{ moment(post[0].date).format("MMMM Do YYYY, h:mm, dddd") }}
      </div>
      <!-- THIS INCLUDES HTML TAGS -->
      <div class="post-content" v-html="post[0].content.rendered"></div>
    </div>
  </div>
  <div class="side-container">
    <div class="side-wrapper">
      <ul v-if="!isLoading" class="blog-posts-ul" v-for="latest in latestPosts">
        <div class="posts-card">
          <a
            ><router-link
              :to="/blog/ + latest.slug"
              key="latest.id"
              class="posts-permalink"
            >
            </router-link
          ></a>
          <img
            v-if="latest.featured_media != 0"
            class="posts-featuredimage"
            :src="latest._embedded['wp:featuredmedia'][0].source_url"
            :alt="latest.title.rendered"
          />
          <img v-else src="@/assets/logos/favicon-big.png" />
          <div class="posts-text">
            <div class="posts-title">{{ latest.title.rendered }}</div>
            <div class="posts-date">
              <p>{{ moment(latest.date).fromNow() + " " + "ago" }}</p>
            </div>
            <div class="posts-author">
              {{ latest._embedded.author[0].name }}
            </div>
          </div>
        </div>
      </ul>
    </div>
  </div>
</template>

Thanks for your time


Solution

  • In my original post as you can see, I'm using axios to get my post with the params queryOptions:

    I declare my queryOptions with a const:

    const queryOptions = {
      slug: route.params.blogSlug,
      _embed: true,
    };
    

    THEN, I call the getPost method, which uses this constant:

    const getPost = () => {
      isLoading.value = true;
      axios
        .get(postsUrl, { params: queryOptions } })
        .then((response) => {
          post.value = response.data;
          console.log("getPost!");
        })
        .catch((error) => {
          console.log(error);
        })
        .then(() => {
          isLoading.value = false;
        });
    };
    

    And the problem is right there: that queryOptions, IS a constant, gets defined at the onMounted lifecycle, or even before I'm not 100% sure, but it does NOT get re-defined WHEN I click on any link on this BlogPost.vue component.

    SO, the solving is the following:

    Change the getPost method so it does NOT use a pre-defined constant, rather just simply type in the params, like the following:

    const getPost = () => {
      isLoading.value = true;
      axios
        .get(postsUrl, { params: { slug: route.params.blogSlug, _embed: true } })
        .then((response) => {
          post.value = response.data;
          console.log("getPost!");
        })
        .catch((error) => {
          console.log(error);
        })
        .then(() => {
          isLoading.value = false;
        });
    };
    

    If I do it this way, every time the getPost method runs, which it WILL since I'm watching the blogSlug change with

    watch(() => route.params.blogSlug, getPost);
    

    the getPost function will always call the latest route.params.blogSlug, which gets changed before the watcher runs the getPost as I click on the sidebar link.