My search button doesn't render new information unless I refresh the page

43 Views Asked by At

I'm using Vue3 to create a weather web app for my project in school. It works fine until when you search for a city and it still gives you info on the last rendered city. Say, I search for Ohio and click on my search button, it renders. Now, if I search for London and click on the search button, the information rendered is still that of Ohio. When the page is refreshed the correct information about London is now rendered.

I'm following a series from John Kormanicki on Vue3.

Here's my Navigation.vue component containing the search bar:

<template>
  <header class="sticky top-0 shadow-md z-50 bg-background">
    <nav class="px-24 flex items-center justify-between gap-3 text-text py-5 max-md:py-4 max-sm:px-2">
      <RouterLink :to="{name: 'home'}">
        <div class="flex items-center gap-3">
          <i class="fa-solid fa-cloud-meatball fa-2xl text-primary"></i>
          <p class="text-3xl font-semibold text-primary max-md:text-xl">ClearVue</p>
        </div>
      </RouterLink>

      <!-- Search Bar -->
      <div class="relative w-[46%]">
        <input v-model="searchQuery" type="text" placeholder="Search for a City" class="py-2 px-2 w-full bg-transparent rounded-md border border-gray-300 focus:border-secondary focus:outline-none placeholder:text-sm placeholder:font-Lexend" @input="getSearchResults">

        <div class="flex items-center justify-center absolute bg-accent w-9 h-9 right-[3px] top-[3px] rounded-[4px] cursor-pointer hover:opacity-80" @click="previewCityFromButton">
          <i class="fa-solid fa-magnifying-glass text-white"></i>
        </div>

        <ul v-if="geoCodingSearchResults && showDropdown" class="absolute bg-gray-200 text-text w-full shadow-sm py-1 px-1 top-[44px] rounded-md">

          <p v-if="searchError">Sorry, something went wrong. Please, try again!</p>
          <p v-if="!searchError && geoCodingSearchResults.length === 0" class="py-1 px-1">No results found. Try a different City.</p>

          <template v-else>
            <li v-for="searchResult in geoCodingSearchResults" :key="searchResult.id" class="py-1 px-1 rounded-[4px] cursor-pointer hover:bg-gray-300 duration-150 ease-out" @click="pushSearchQuery(searchResult)">
              {{ `${searchResult.name}, ${searchResult.state}, ${searchResult.country}` }}
            </li>
          </template>
        </ul>
      </div>

      <!-- Nav Links -->
      <ul class="flex items-center gap-8 font-semibold text-text max-md:text-sm max-sm:hidden">
        <li class="hover:text-secondary duration-150 ease-out cursor-pointer" @click="openModal('about')">About ClearVue</li>
        <li class="hover:text-secondary duration-150 ease-out cursor-pointer" @click="openModal('how')">How it works</li>
      </ul>

      <Modal :modalActive="modalActive" @close-modal="toggleModal">
        <div class="text-text" v-if="selectedLink === 'about'">
          <h1 class="text-xl mb-4 font-semibold">
            About ClearVue:
          </h1>
          <p>ClearVue is a user-friendly and intuitive weather application designed to provide you with up-to-date weather information at your fingertips.<br><br> Whether you're planning your day, a trip, or simply want to stay informed about the weather conditions, ClearVue has you covered.</p>
        </div>
        <div class="text-text" v-if="selectedLink === 'how'">
          <h1 class="text-xl mb-4 font-semibold">
            How it works:
          </h1>
          <p>
            ClearVue is a user-friendly weather application that provides accurate and real-time weather information. Here's how it works:
          </p>
          <ul>
            <li>1. Start by entering a location to search for.</li>
            <li>2. View current weather conditions, including temperature, wind speed, humidity, and more.</li>
            <li>3. Track a City to view at a later time.</li>
          </ul>
        </div>
      </Modal>

    </nav>
  </header>
</template>
<script setup>
  import { RouterLink } from 'vue-router';
  import Modal from './Modal.vue';
  import { ref } from 'vue';
  import { useRouter } from 'vue-router';
  import axios from 'axios';

  // Modal scripts
  const modalActive = ref(false);
  const selectedLink = ref(null);

  const openModal = (link) => {
    selectedLink.value = link;
    modalActive.value = true;
  }

  const toggleModal = () => {
    modalActive.value = !modalActive.value;
  };

  // Input scripts
  const searchQuery = ref("");
  const queryTimeout = ref(null);
  const geoCodingAPIKey = 'e7a0834d5c68a78d2a20c4ade200a188';
  const geoCodingSearchResults = ref(null);
  const searchError = ref(null);

  const getSearchResults = () => {
    clearTimeout(queryTimeout.value);
    queryTimeout.value = setTimeout(async () => {
      if (searchQuery.value !== "") {
        try {
          const result = await axios.get(`http://api.openweathermap.org/geo/1.0/direct?q=${searchQuery.value}&limit=5&appid=${geoCodingAPIKey}`);
          geoCodingSearchResults.value = result.data;
        } catch {
          searchError.value = true;
        }
        return;
      };
      geoCodingSearchResults.value = null;
    }, 200)
  };

  let lastSelectedSearchResult = ref(null);

  const previewCityFromButton = () => {
    if (lastSelectedSearchResult.value) {
      previewCity(lastSelectedSearchResult.value);
    }
    searchQuery.value = ""
  };
  
  const showDropdown = ref(true);

  const pushSearchQuery = (searchResult) => {
    searchQuery.value = `${searchResult.name}, ${searchResult.state}, ${searchResult.country}`;
    showDropdown.value = false;
    lastSelectedSearchResult.value = searchResult;
  };

  const router = useRouter();

  const previewCity = (searchResult) => {
    console.log(searchResult);
    const city = searchResult.name;
    const state = searchResult.state;
    router.push({
      name: 'cityView',
      params: { state: state, city: city },
      query: {
        lat: searchResult.lat,
        lng: searchResult.lon,
        preview: true
      }
    });
  }

</script>

And here's my AsyncCityView.vue component where the data is rendered:

<template>
  <div class="flex flex-col flex-1 items-center text-text">
    <!-- Banner -->
    <div v-if="route.query.preview && showBanner" class="flex items-center justify-end gap-[380px] text-text py-3 px-8 bg-blue-100 w-full text-center max-sm:gap-0">
      <p>You are currently previewing this City. Click the "+" icon to track this City.</p>
      <i class="fa-solid fa-circle-xmark cursor-pointer hover:opacity-80 duration-300 ease-out" @click="closeBanner"></i>
    </div>

    <!-- Overview -->
    <div class="flex flex-col items-center text-text py-12">
      <div class="flex relative">
        <h1 class="text-4xl mb-4">{{ route.params.city }}</h1>
        <i class="fa-solid fa-circle-plus text-xl absolute top-2 right-[-6rem] cursor-pointer text-accent hover:text-2xl duration-200 ease-in-out"></i>
      </div>
      <p class="text-9xl mb-6">
        {{ Math.round(weatherData.data.main.temp) }}&deg;
      </p>
      <p>
        Feels like: {{ Math.round(weatherData.data.main.feels_like) }}&deg;
      </p>
      <p class="capitalize">
        {{ weatherData.data.weather[0].description }}
      </p>
      <img class="w-48 h-auto bg-secondaryLight rounded-full mt-6" :src="`http://openweathermap.org/img/wn/${weatherData.data.weather[0].icon}@2x.png`" alt="Image of Current Weather">
    </div>

    <hr class="border-text border-opacity-10 border w-[48rem]">

    <div class="flex items-center gap-8 py-8">
      <!-- Wind Readings -->
      <div class="flex flex-col items-center">
        <img class="w-10 h-auto p-2 bg-accent rounded-md m-2" src="../assets/img/wind.svg" alt="">
        <p class="font-bold text-lg">Wind</p>
        <p class="opacity-90"> {{ weatherData.data.wind.speed +'km/h' }}</p>
      </div>
      
      <!-- Humidity Readings -->
      <div class="flex flex-col items-center">
        <img class="w-10 h-auto p-2 bg-accent rounded-md m-2" src="../assets/img/humidity.svg" alt="">
        <p class="font-bold text-lg">Humidity</p>
        <p class="opacity-90"> {{ weatherData.data.main.humidity +'%' }}</p>
      </div>

      <!-- Pressure Readings -->
      <div class="flex flex-col items-center">
        <img class="w-10 h-auto p-2 bg-accent rounded-md m-2" src="../assets/img/pessure.svg" alt="">
        <p class="font-bold text-lg">Pressure</p>
        <p class="opacity-90"> {{ weatherData.data.main.pressure +'hPa' }}</p>
      </div>
    </div>
  </div>
</template>
<script setup>
  import axios from 'axios';
  import { useRoute } from 'vue-router';
  import { ref } from 'vue';

  const showBanner = ref(true);
  const closeBanner = () => {
    showBanner.value = false;
  };

  const apiKey = 'e7a0834d5c68a78d2a20c4ade200a188';
  const route = useRoute();
  const getWeatherData = async () => {
    try {
      const weatherData = await axios.get(`https://api.openweathermap.org/data/2.5/weather?lat=${route.query.lat}&lon=${route.query.lng}&appid=${apiKey}&units=metric`);

      return weatherData;

    } catch(err) {
      console.log(err);
    }
  }

  const weatherData = await getWeatherData();
  console.log(weatherData);
</script>

As I am not proficient in Vue3, I asked ChatGPT for help. It didn't workout.

0

There are 0 best solutions below