well i'm new to vue js and developing an application with a search function.
This is my component where the search results will be rendered.
<script>
import RecipeItem from "../recipe/RecipeItem";
import { baseApiUrl } from "#/global";
import axios from "axios";
import PageTitle from "../template/PageTitle";
export default {
name: "Search",
components: { PageTitle, RecipeItem },
data() {
return {
recipes: [],
recipe: {},
search: '',
}
},
methods: {
getRecipes() {
const url = `${baseApiUrl}/search?search=${this.search}`;
axios(url).then((res) => {
this.recipes = res.data;
});
}
},
watch: {
search() {
const route = {
name: 'searchRecipes'
}
if(this.search !== '') {
route.query = {
search: this.search
}
}
},
'$route.query.search': {
immediate: true,
handler(value) {
this.search = value
}
}
},
};
</script>
<template>
<div class="recipes-by-category">
<form class="search">
<router-link :to="{ path: '/search', query: { search: search }}">
<input v-model="search" #keyup.enter="getRecipes()" placeholder="Search recipe" />
<button type="submit">
<font-icon class="icon" :icon="['fas', 'search']"></font-icon>
</button>
</router-link>
</form>
<div class="result-search">
<ul>
<li v-for="(recipe, i) in recipes" :key="i">
<RecipeItem :recipe="recipe" />
</li>
</ul>
</div>
</div>
</template>
ok so far it does what it should do, searches and prints the result on the screen.
But as you can see I created the search field inside it and I want to take it out of the search result component and insert it in my header component which makes more sense for it to be there.
but I'm not able to render the result in my search result component with my search field in the header component.
but I'm not able to render the result in my search result component with my search field in the header component.
Header component
<template>
<header class="header">
<form class="search">
<input v-model="search" #keyup.enter="getRecipes()" placeholder="Search recipe" />
<router-link :to="{ path: '/search', query: { search: this.search }}">
<button type="submit">
<font-icon class="icon" :icon="['fas', 'search']"></font-icon>
</button>
</router-link>
</form>
<div class="menu">
<ul class="menu-links">
<div class="item-home">
<li><router-link to="/">Home</router-link></li>
</div>
<div class="item-recipe">
<li>
<router-link to="/"
>Recipes
<font-icon class="icon" :icon="['fa', 'chevron-down']"></font-icon>
</router-link>
<Dropdown class="mega-menu" title="Recipes" />
</li>
</div>
<div class="item-login">
<li>
<router-link to="/auth" v-if="hideUserDropdown">Login</router-link>
<Userdropdown class="user" v-if="!hideUserDropdown" />
</li>
</div>
</ul>
</div>
</header>
</template>
Result component
<template>
<div class="recipes-by-category">
<div class="result-search">
<ul>
<li v-for="(recipe, i) in recipes" :key="i">
<RecipeItem :recipe="recipe" />
</li>
</ul>
</div>
</div>
</template>
Keep the state variables in whatever parent component is common to both the header component and the results component. For example, if you have a Layout component something like this:
<!-- this is the layout component -->
<template>
<HeaderWithSearch v-on:newResults="someFuncToUpdateState" />
<ResultsComponent v-bind:results="resultsState" />
</template>
<!-- state and function to update state are in a script here... -->
When the search bar returns results you need to pass that data "up" to the parent component with an $emit call, then the parent component can then pass that state back down to the results component using normal props.
Check out this documentation: https://v2.vuejs.org/v2/guide/components-custom-events.html
Be sure to pay special attention to the .sync part of the documentation and determine if that's something you need to implement as well.
Unless you want to use a more complicated state management library like vuex (which shouldn't be necessary in this case) you can just keep state in a common parent and use $emit to pass up and props to pass down.
Related
I have a todo app that I am working on which I divided it into two component one for form and the other for the todo list. in todo list I have a Method that delete the todo but it delete two todo instead of one I tried to console log the component that I click on and found that it get logged twice. I tried prevent method on click solution i found it here in stackoverflow but it didn't work.
<template>
<div class="todo" v-for="(todo, index) in todos" :key="index">
<div class="todo-text">{{ todo.text }}</div>
<div class="IconDiv">
<fa #click.prevent="deleteTodo(index)" class="icon" icon="trash-alt" />
<fa #click="completeTodo(index)" class="icon" icon="edit" />
<fa class="icon" icon="check-square" />
</div>
</div>
</template>
<script>
export default {
name: "TodoList",
emits: ["removeTodo", "completEmit"],
props: {
msg: String,
todos: Array,
val: String
},
data() {
return {};
},
methods: {
deleteTodo(index) {
return console.log(this.todos[index].text);
}
}
};
</script>
I have a web app which shows the blog posts in a grid. But the BlogCards component in the Home.vue just outputs nothing, whereas it should output the blogs in a grid format. All the datas are stored in firebase. If I go to /blogs, I can see the blogs in grid format, but it doesn't work on the Home.vue. It also spits out the Vue Warn: property or method "blogPostsCards" is not defined on the instance but referenced during render.
I took this code from this tutorial at 5:31:05 minute mark.
Any solution to this problem.
Home.vue
<template>
<div class="home">
<BlogPost :post="post" v-for="(post, index) in blogPostsFeed" :key="index" />
<div class="blog-card-wrap">
<div class="container">
<h3>View more recent blogs</h3>
<div class="blog-cards">
<BlogCards :post="post" v-for="(post, index) in blogPostsCard" :key="index" />
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import BlogPost from '../components/BlogPost.vue'
import BlogCards from '../components/BlogCards.vue'
export default {
name: "Home",
components: {
BlogPost,
BlogCards,
Arrow
},
computed : {
blogPostsCards() {
return this.$store.getters.blogPostsCards;
},
blogPostsFeed() {
return this.$store.getters.blogPostsFeed;
},
}
};
</script>
BlogCards.vue
<template>
<div class="blog-card">
<img :src="post.blogCoverPhoto" alt="">
<div class="info">
<h4>{{ post.blogTitle }}</h4>
<h6>Posted on: {{ new Date(post.blogDate).toLocaleString('en-us', {dateStyle: "long"})}}</h6>
<router-link class="link" to="#" >
View Post <Arrow class="arrow" />
</router-link>
</div>
</div>
</template>
<script>
export default {
name: "blogCard",
props: ["post"],
computed: {
editPost() {
return this.$store.state.editPost
},
}
}
</script>
And getter function in store/index.js
getters:{
blogPostsFeed(state){
return state.blogPosts.slice(0,2);
},
blogPostsCards(state) {
return state.blogPosts.slice(2,6);
},
},
<BlogCards :post="post" v-for="(post, index) in blogPostsCard" :key="index" />
In your Home.vue >> change blogPostsCard to blogPostsCards because you use blogPostsCards in your computed so it gives you that error.
I am working on a small Todo App with Vue 3.
In App.vue I have the 3 child components and the methods:
<template>
<div id="app">
<Header/>
<List/>
<Footer/>
</div>
</template>
<script>
import axios from 'axios'
import Header from './components/Header.vue'
import List from './components/List.vue'
import Footer from './components/Footer.vue'
export default {
name: 'App',
components: {
Header,
List,
Footer
},
props: ['todos'],
data() {
return {
url: "https://jsonplaceholder.typicode.com/todos?&_limit=5",
dataIsLoaded: false,
isValidInput: true,
todos: [],
unsolvedTodos: [],
newTitle: "",
}
}
}
</script>
In List.vue I have:
<template>
<div>
<h1>List</h1>
<ul class="todo-list" v-if="dataIsLoaded">
<TodoItem v-for="todo in todos.slice().reverse()" :key="todo.id" v-bind:class="{done: todo.completed}" :todo="todo" />
</ul>
</div>
</template>
<script>
import TodoItem from "./TodoItem.vue";
export default {
name: 'List',
components: { TodoItem },
props: ['todos']
}
</script>
In TodoItem.vue:
<template>
<li>
<input type="checkbox" :checked="todo.completed" #change="toggleTodo(todo)" />
<span class="title">{{todo.title}}</span>
<button #click="deleteTodo(todo.id)">
<i class="fa fa-trash" aria-hidden="true"></i>
</button>
</li>
</template>
<script>
export default {
name: 'TodoItem',
props: ['todo']
}
</script>
The todo list's <h1> heading is displayed, but the unordered list of todos is not.
Introducing props: ['todos'] in App.vue throws the error Duplicated key 'todos'.
What am I missing?
On your App.vue you defined props['todos'] and at the same time todos is defined in your data(). That might be the one causing the error.
Looks like you forgot to define dataIsLoaded in List.vue. It either needs to be defined in data or be a computed property.
I have a problem with passing data from my rest API to the child component,
I have a list with many records, after a click on title i want to open another page with details of this clicked element, here is my code:
My router in table:
<router-link
to="/user/details"
v-slot="{ href, route, navigate }"
>
<a
:href="href"
#click="navigate"
>
{{ user.name }}
</a>
</router-link>
My user/details page:
<template>
<div>
<div class="flex flex-wrap">
<div class="w-full mb-12 xl:mb-0 px-4">
//HOW TO USE THIS MY user.name FROM ROUTER?
</div>
</div>
</div>
</template>
<script>
export default {
components: {}
};
</script>
{
path: "/user/details",
component: UserDetails
}
You can pass the name in the query when switching routes as follows:
<router-link
:to="{ path: '/user/details', query: { name: user.name } }"
>
User Details
</router-link>
And then parse it in the details component as follows:
data: () => ({ userName: "" }),
created() {
this.userName = this.$route.query.name;
}
The $router object exposes you to the query params of the url
this.$route.query
https://router.vuejs.org/api/#router-link
I'm having an issue when using a child component, a list does not update based on a prop passed into it.
If the comments array data changes, the list will not update when it uses a child component <comment></comment>.
TopHeader template:
<template>
<ul v-for="comment in comments">
// If don't use a child component, it updates whenever `comments` array changes:
<li>
<div>
/r/{{comment.data.subreddit}} ·
{{comment.data.score}}
</div>
<div class="comment" v-html="comment.data.body"></div>
<hr>
</li>
</ul>
</template>
TopHeader component:
import Comment from 'components/Comment'
export default {
name: 'top-header',
components: {
Comment
},
data () {
return {
username: '',
comments: []
}
},
methods: {
fetchData: function(username){
var vm = this;
this.$http.get(`https://www.reddit.com/user/${username}/comments.json?jsonp=`)
.then(function(response){
vm.$set(vm, 'comments', response.body.data.children);
});
}
}
}
However, if I use a child component it does not update.
Modified TopHeader template:
<template>
<ul v-for="comment in comments">
// If I instead use a component with prop data, it does not update
<comment :data="comment.data"></comment>
</ul>
</template>
Comment child template:
<template>
<li>
<div>
/r/{{subreddit}} ·
{{score}}
</div>
<div class="comment" v-html="body"></div>
<hr>
</li>
</template>
Comment child component:
export default {
name: 'comment',
props: ['data'],
data() {
return {
body: this.data.body,
subreddit: this.data.subreddit,
score: this.data.score,
}
}
}
Move the v-for statement to the component <comment>. With the v-for on the ul-tag you would repeat the ul tag. See http://codepen.io/tuelsch/pen/YNOqYR again for an example.
<div id="app">
<button v-on:click="fetch">Fetch data</button>
<ul>
<comment v-for="comment in comments" v-bind:comment="comment" />
</ul>
</div>