vue v-for array isn't updating - vue.js

I am making simple chat App
Input name from index.vue
const newUser = {
id: this.$store.state.user.stateWebSocket.getUniqueID(),
name: this.username,
room: this.room,
};
this.$store.commit("user/putUser", newUser);
Show userList on chat.vue
<ul id="users">
<li v-for="user in userList" :key="user.id">
{{ user.name }}
</li>
</ul>
Get userList on chat.vue by using computed from store
userList() {
return this.$store.state.user.users;
},
store array users on user.js
putUser(state, user) {
// state.newUser = user;
// console.log("state newUser: ", state.newUser);
state.users.push({
id: user.id,
name: user.name,
room: user.room,
});
console.log("users array on users.js: ", state.users);
},
state:{
users: [],
}
For example, when first user has joined, I can see first user is on userList
<li v-for="user in userList" :key="user.id">
{{ user.name }}
</li>
and after Second user has joined, I can see the first user and Second user are on the userList; however It doesn't reflect on the first user's view.
After when I press f5 button(refresh), It shows on the userList.
What I want is to reflect as new user has joined without any refresh.
Comments will be helpful.

Related

I want to display an array value based on the condition of another array value. What is the best way of doing this?

I have an array of users, but the users have different roles. One user can be an Admin.
I have a welcome message that will pop up when a user log in. The problem is that the system admin has access to all users and can switch between their accounts when doing support cases. This means that the welcome message would disaplay ALL user names when an admin logged in.
So I made a computed property that filters the list of current users, so that only the admin name gets shown, but now if a normal user logs in, no name will be shown.
Is there a way to make an if/else statement that tells the application: If current user is system admin, use the filtered property. If not, then just show the current user.
This is my code.
<template>
<b-container>
<b-modal
centered
v-model="isVisible">
<b-row v-for="user in filterUser">
<b-col class="text-center">
<h2>{{ $t('welcomeTo') + ' ' }}<span style="color: #31a58e">website name</span> {{' ' + user.name }}</h2>
</b-col>
</b-row>
</b-modal>
</b-container>
</template>
This my array and properties. I am actually getting the current user from axios, but for the sake of simplicity, I made a hardcoded array so it's easier to follow.
<script>
data() {
return {
isVisible: true,
currentUser: [
{
systemAdmin: true,
name: 'Michael'
},
{
systemAdmin: false,
name: 'Marie'
},
{
systemAdmin: false,
name: 'Martin'
}
],
user: {},
};
},
computed: {
filterUser() {
return this.currentUser.filter(user => user.systemAdmin)
},
}
</script>
Here is what I tried:
filterUser() {
if(this.currentUser.systemAdmin) {
return this.currentUser.filter(user => user.systemAdmin)
} else {
return this.currentUser
}
},
Your filterUser property is returning an array of users if the user is an admin, but a single user otherwise. Change filterUser so that in both cases it either returns an array or a single user object.
In this case, you expect to only have one admin user in the array, so you can use currentUser.find instead of currentUser.filter to return only the admin user.
filterUser() {
if(this.currentUser.systemAdmin) {
return this.currentUser.find(user => user.systemAdmin)
} else {
return this.currentUser
}
},
Now that filterUser is returning a single user, you can remove the v-for from b-row and display the single user's name:
<b-row>
<b-col class="text-center">
<h2>{{ $t('welcomeTo') + ' ' }}<span style="color: #31a58e">website name</span> {{' ' + filterUser.name }}</h2>
</b-col>
</b-row>

update my header accroding to login state

I am trying to make my header show or not show some nav-item according to the login state(using v-if). However, when I route to the page that logged in, the data to determine whether user logged in didn't update immediately, I need to refresh to update the data.
Expected:
before login, showing: Home Login
after login, showing: Home Listing Logout
Realistic:
before login, showing: Home Login
after login, showing: Home Login
Refresh the page, showing: Home Listing Logout
MyHeader.vue
<template>
<nav class="navbar navbar-expand navbar-light sticky-top">
<div class="container">
<img id="HA-Logo" src="#/assets/HALogo_En.gif" alt="HA Logo" class="">
<div>
<ul class="navbar-nav ml-auto">
<li class="nav-item">
<router-link to="/" class="nav-link">{{ $t('Home') }}</router-link>
</li>
<li class="nav-item">
<router-link v-if="!currentUser" to="/HALogin" class="nav-link">HALogin</router-link>
</li>
<li class="nav-item">
<router-link v-if="!currentUser" to="/SupLogin" class="nav-link">SupLogin</router-link>
</li>
<li class="nav-item">
<router-link v-if="currentUser" to="/Listing" class="nav-link">Listing</router-link>
</li>
<li class="nav-item">
<router-link v-if="currentUser" #click="logout" to="/" class="nav-link">Logout</router-link>
</li>
<li class="nav-item">
<a class="nav-link"><LocaleSwitcher /></a>
</li>
<button #click="check">bug</button>
</ul>
</div>
</div>
</nav>
</template>
<script>
import Cookies from 'js-cookie';
import LocaleSwitcher from './LocaleSwitcher.vue';
import SideNav from './SideNav.vue';
export default {
name: "MyHeader",
components: { LocaleSwitcher, SideNav },
data(){
return{
currentUser: Cookies.get('login')
}
},
computed: {
loggedin(){
return typeof Cookies.get('login') === 'undefined' ? false : true
}
},
methods: {
logout(){
console.log("Cookies.get('login'): ", Cookies.get('login'));
Cookies.remove('login');
},
check(){
console.log("loggedin: ",this.loggedin);
console.log("typeof Cookies.get('login') === 'undefined': ", typeof Cookies.get('login') === 'undefined')
console.log("cookies: ",Cookies.get('login'))
}
},
}
</script>
You can use lifecycle hook like beforeCreate go get data from cookies.
export default {
name: "MyHeader",
components: { LocaleSwitcher, SideNav },
data(){
return{
currentUser: Cookies.get('login')
}
},
beforeCreate() {
this.currentUser = Cookies.get('login')
},
computed: {
loggedin(){
return typeof Cookies.get('login') === 'undefined' ? false : true
}
},
...
}
beforeCreate is called immediately when the instance is initialized, after props resolution, before processing other options such as data() or computed.
cookies are not reactive so Vue will not detect them changing which is why a refresh is required. You will either need a library like vue-cookies-reactive or try a different strategy. One alternative could be to store the logged in user data in a store (e.g. vuex or pinia) so when the cookie is set, you also set data in the store, and since the store state is reactive if you watch that instead of the cookie you'll have the desired reactive update to your page. Cookies (and localStorage) are still a good way to persist state across page refreshes so it's not a bad idea to use both cookies and a store.

NuxtJS + axios GET not querying for parameters

I have a table resources with severals records. I'm trying to build a form with a button so the user can query it, but I can't get the first piece to work, which is building a custom query with params. In the example, I tried to have it fetch only id = 1, but it returns all records and I don't know why.
<ul>
<li v-for="resource in resources">
Facility: {{resource.facility}}
Type: {{ resource.resource_type }}
ID: {{ resource.id }}
</li>
</ul>
</template>
<script>
export default {
data: function () {
return {
resources: []
}
},
fetch: async function() {
this.resources = await this.$axios.$get('/resources/',
{
params: {
id: 1, // this does not only filter facilities with this id
}
})
}
}
</script>

VueJS replace data of component // same route problem

i have a '/posts' route. This 'posts' route has a created() function which fetches data from an API via GET and outputs the data to the page.
Now I have a navbar which is included on every page. This navbar now has an input field where I can search certain posts by tags. This tag based search function is already working and runs via POST to an api.
Now the problem:
I write some tags into input field in the navigation and search for them. If I'm currently not at the posts route, the search works fine and I get directed to the posts route and see the tag related posts.
If I write some tags in the navbar input field and press the search button, WHILE i'm already on the posts route, nothing happens.
So if I'm in any other route then '/posts', the tag based search works great.
Thats why I think, the problem is, that I'm already on the '/posts' route. But it should also work this way! So I need something like a route link that replaces/refresh the route content?
Here is my code:
Relevant part of my navbar component:
<ul class="navbar-nav mr-auto">
<li class="nav-item">
<router-link to="/posts" class="nav-link">Posts</router-link>
</li>
</ul>
<form class="form-inline my-2 my-lg-0">
<div v-for="(tag, index) in tags" class="ml-sm-2">
<h6><span class="badge badge-light" #click="removeSearchTags(index)">{{ tag }}</span></h6>
</div>
<input class="form-control ml-1 mr-sm-2" type="text" v-model="tag" v-on:keyup.enter="pushToTags"
placeholder="Search Gaming, Youtube, DrunkSlut" aria-label="Search">
<router-link :to="{name: 'posts', params: { searchTags: tags }}" reload>
<button type="button" v-if="this.tags.length > 0"
class="btn btn-outline-light my-2 my-sm-0">Search
</button>
</router-link>
</form>
Whole posts component logic:
<script>
export default {
name: "posts",
data: function () {
return {
apiUrl: '/getPosts',
posts: '',
submitted: false,
first_page_url: '',
last_page_url: '',
current_page_url: '',
next_page_url: '',
prev_page_url: '',
lastPage: '',
current_page: '',
tags: [],
}
},
methods: {
getPosts: function (url) {
this.$http.get(url).then(function (data) {
this.posts = data.body.data;
this.first_page_url = data.body.first_page_url;
this.last_page_url = data.body.last_page_url;
this.next_page_url = data.body.next_page_url;
this.current_page = data.body.current_page;
this.prev_page_url = data.body.prev_page_url;
this.lastPage = data.body.last_page;
this.current_page_url = '/getPosts?page=' + this.current_page;
});
},
getPostByTags: function (url, tags) {
this.$http.post(url, {
tags: tags
}).then(function (data) {
this.posts = data.body.data;
this.first_page_url = data.body.first_page_url;
this.last_page_url = data.body.last_page_url;
this.next_page_url = data.body.next_page_url;
this.current_page = data.body.current_page;
this.prev_page_url = data.body.prev_page_url;
this.lastPage = data.body.last_page;
this.current_page_url = '/getPostByTags?page=' + this.current_page;
});
},
},
computed: {},
created() {
if (!this.$route.params.searchTags) {
this.getPosts(this.apiUrl);
} else {
this.getPostByTags('/getPostByTags', this.$route.params.searchTags);
}
},
}
</script>
The Main html file, where vueJS starts. There is only the navbar component, thats how it's included on any other route.
<div id="app">
<navbar></navbar>
<router-view></router-view>
</div>
Try to make your created as a watch
Something like:
watch: {
'$route.params.searchTags': {
deep: true,
immediate: true, // this triggers the watch on component creation, so you can remove the created hook content
handler() {
if (!this.$route.params.searchTags) {
this.getPosts(this.apiUrl);
} else {
this.getPostByTags('/getPostByTags', this.$route.params.searchTags);
}
}
}
}

How to handle different input values created with v-for

I was wondering how to handle different input values when they have been created with v-for. In this example, let's say I have some tweets from different users, each having their own reply field. How can I treat the input values as separate entities?
In this Fiddle, you can see the input values bind to the other input values. How could I pass the value of the selected field to the the reply method without the current binding side-effect? Thanks.
JS Fiddle: https://jsfiddle.net/n2fole00/n60d4qos/
HTML
<div id="app" style="margin:10px;">
<div v-for="(tweet, key) in tweets">
<p><strong>{{ tweet.user }} wrote...</strong></p>
<p>{{ tweet.text }}</p>
<label v-bind:for="'reply-'+key">Reply: </label>
<input v-bind:id="'reply-'+key" v-model="replies" type="text">
<button v-on:click="reply(replies, tweet.user)">Reply</button>
<hr>
</div>
</div>
Vue
new Vue({
el: "#app",
data: {
replies: [],
tweets: [
{ text: "Learn JavaScript", user: "Bob" },
{ text: "Learn Vue", user: "Alice"},
{ text: "Play around in JSFiddle", user: "John" },
]
},
methods: {
reply(reply, user) {
alert("You replied\n" + reply + "\nto " + user);
},
},
})
In a larger/production app, I would probably make each user field its own component (or two, one for the tweet and one for the reply, both orchestrated by this parent).
in this case, you can use the key as the array index for replies and get only the relevant information. See the updated fiddle.
in template:
<input v-bind:id="'reply-'+key" v-model="replies[key]" type="text">
<button v-on:click="reply(replies, tweet.user, key)">Reply</button>
in methods:
reply(reply, user, key) {
alert("You replied\n" + reply[key] + "\nto " + user);
},
https://jsfiddle.net/n60d4qos/9/