1

I have been working on a news app, using Vue 3 and the News API.

I am currently working on a search functionality.

In App.vue I have:

<template>
  <TopBar @search="doSearch" />
  <div class="container">
     <HomeView searchString="searchTerm" v-if="!searchTerm.length" />
     <SearchResultsView searchString="searchTerm" v-if="searchTerm.length" />
  </div>
  <AppFooter />
</template>

<script>
import TopBar from '@/components/TopBar.vue';
import AppFooter from '@/components/AppFooter.vue';
import HomeView from '@/views/HomeView.vue';
import SearchResultsView from '@/views/SearchResultsView.vue';

export default {
  name: 'App',
  components: {
    TopBar,
    AppFooter,
    HomeView,
    SearchResultsView
  },

  data: () => ({
    searchTerm: ''
  }),

  methods: {
    doSearch: function(searchTerm) {
      this.searchTerm = searchTerm;
      console.log(this.searchTerm);
    }
  }
}
</script>

I emit the search event form the TopBar.vue component, where the search form is:

<template>
  <nav class="navbar py-1 sticky-top navbar-expand-md">
    <div class="container-fluid">
      <form ref="searchForm" class="search_form w-100 mx-auto mt-2 mt-md-0">
          <div class="input-group">
            <input
              @change="handleChange"
              v-model="searchTerm"
              class="form-control search-box"
              type="text"
              placeholder="Search..."
            />
            <div class="input-group-append">
              <button class="btn" type="button">
                <font-awesome-icon :icon="['fas', 'search']" />
              </button>
            </div>
          </div>
        </form>
    </div>
  </nav>
</template>

<script>
export default {
  name: "TopBar",

  methods: {
    handleChange(event){
      this.$emit('search', event.target.value)
    }
  }
};
</script>

The search string is correctly "captured" by the root App.vue component. I try to pass it to the ArticleList.vue component, so that it becomes a part of the endpoint used by the component:

<template>
  <div v-if="articles.length" class="row">
    <div
      v-for="article in articles"
      :key="article._id"
      class="col-xs-12 col-sm-6 col-lg-4 col-xl-3"
    >
      <ArticleCard :article="article" />
    </div>
  </div>
  <p v-else class="text-center">
    No articles to display
  </p>
</template>

<script>

import ArticleCard from './ArticleCard.vue';

export default {
  name: "NewsList",
  components: {ArticleCard},

  props: {
    whatToShow: {
      type: String,
      required: true,
    },

    searchString: {
      type: String,
      required: true,
      default: ''
    }
  },

  data: () => ({
    language: 'en',
    page_size: 24,
    current_page: 1,
    articles: [],
  }),

  mounted() {
    this.getArticles();
  },

  methods: {
    getArticles() {
      let endpoint = `${process.env.VUE_APP_API_URL}/${this.$props.whatToShow}?q=${this.$props.searchString}&language=${this.language}&page_size=${this.page_size}&page=${this.current_page}&apiKey=${process.env.VUE_APP_API_KEY}`;

      console.log(endpoint);

      this.$axios
        .get(endpoint)
        .then((response) => {
          this.articles = response.data.articles;
          console.log(this.articles);
        })
        .catch((err) => console.log(err));
    },
  }
};

Screenhot

enter image description here

The problem

The searchString prop in the endpoint variable above does not update upon doing a search (with "money" for example) and console.log(endpoint) outputs

https://newsapi.org/v2/everything?q=&language=en&page_size=24&page=1&apiKey=myappykey123secret

instead of

https://newsapi.org/v2/top-headlines?q=money&language=en&page_size=24&page=1&apiKey=myappykey123secret

Questions

  1. What am I doing wrong?
  2. What is the most reliable way to fix this issue?
Razvan Zamfir
  • 4,209
  • 6
  • 38
  • 252
  • Another aproach is to call a method on the child which for me is better https://stackoverflow.com/questions/42632711/how-to-call-function-on-child-component-on-parent-events – Mathias F Aug 18 '23 at 10:54

5 Answers5

4

Yes you are correct, searchString updated in App.vue but you are not watching this change in ArticleList component.

The request method getArticles only trigger on mounted. You have to watch searchString prop and trigger getArticles() method again

Tanzer Atay
  • 136
  • 4
  • I added this in *ArticleList.vue*: `watch: { searchString: function() { this.getArticles() } }`. It did not achive the desired result. – Razvan Zamfir Aug 18 '23 at 16:15
  • Also you have change props(missing colon). Use `:searchString="searchTerm"` both HomeView and SearchResultsView. – Tanzer Atay Aug 21 '23 at 09:45
2

Here are the errors I found:

  • In TopBar: add @submit.prevent on the <form> to prevent a page reload when pressing enter in the input
  • In SearchResult: Declare the searchString prop and pass it on to ArticleList
  • In ArticleList: Add watchers on searchString and whatToShow to send requests when these properties change. Also, there is no this.$props, props are available directly, i.e. this.searchString instead of this.$props.searchString

Seems to work now, but there are some obvious errors, like missing vue-router or not declaring a missing prop in HomeView. Those are explained in the console, so it shouldn't be hard to fix them.

Also, .env variables are not available, couldn't tell you why. I assume you accidentally picked a sandbox base which uses the outdated vue-cli instead of create-vue. When I replace the variables with content, the calls fail, but this does not seem related to Vue. Check the URL in the console to see how it looks.

Here is the updated sandbox

Moritz Ringler
  • 9,772
  • 9
  • 21
  • 34
2

I'm surprised nobody pointed out the obvious: most of the problems you're having stem from trying to accomplish the functionality of a store without using one.

Yes, it can be done, and you were on the right track: use <App /> as the one source of truth and sync its state with deeper nested components which are the actual controls.

But "it can be done" in the same way you can walk in between two distant towns.
Should you do it? Probably not. Better use a car or a train.

Apart from not having a store, the app has a few errors, most stemming from applying Vue 2 solutions to a Vue 3 app, without going through the migration guide. Yes, a lot of things work the same in them, but a few have changed.

Here's how a basic store would look like for your app.

A few pointers:

  • I wouldn't place the logic to compose the query url inside the articles list component. That logic should sit inside the store and then you can link various controls from around the app for any of its settings (search term, pagination, category, language, etc...) without the need of those components having to be placed inside the list component.
    The list component should only be in charge or rendering the list of available articles coming from the store. There are many benefits from decoupling the business logic from the UI logic, too many to list here.
  • use @submit.prevent on the nav form, so the page doesn't reload when pressing Enter in the input
  • do not use v-model and @change (or @input) on the same element (v-model is shorthand for :value + @input`)
  • read the docs carefully about how to install and use any plugins (vue-axios and vue-router implementations were both broken in the sandbox you shared).
  • you might want to debounce the searchTerm and only send a query when the user stopped typing
tao
  • 82,996
  • 16
  • 114
  • 150
1

The code has too many errors:

  1. as @BoussadjraBrahim pointed out you don't bind prop in App.vue
  2. in TopBar.vue you have ref='searchForm' and v-model='searchTerm' but the refs searchFormandsearchTerm` are not defined at all.
  3. in TopBar.vue you didn't declare for emits so custom event search will never be caught by doSearch function
  4. in ArticleList.vue you don't even listen for changes so only when this component is mounted does it send a request to endpoint and it doesn't react at all
Tachibana Shin
  • 2,605
  • 1
  • 5
  • 9
1

Where do you try to send the results to NewsList component from ArticleList.vue? You did not post where do you do the binding.