4

So this is my user list component for Vue. Socket.io returns a list of currently active users for example [{name:Fluxed,rank:Admin}] and I want it to auto-update the element. How can I update the prop element then make it show a change?

Here is my code

<template>
  <div id="app">
    <div>
    <div style="float:right;width:30%;border-left:1px solid black;padding:1%;">
      <b>Users</b>
      <b-list-group style="max-width: 300px;" >
        <b-list-group-item class="align-items-center" v-for="value in userList2" v-bind:key="value.name" >
          <b-avatar class="mr-3"></b-avatar>
          <span class="mr-auto">{{ value.name }}</span>
          <b-badge>{{ value.rank }}</b-badge>
        </b-list-group-item>
      </b-list-group>
      <ul class="list-group">
      </ul>
    </div>
  </div>
  </div>
</template>

<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.4/jquery.min.js"></script>

<script>
import io from 'socket.io-client';
import $ from 'jquery'
var socket = io('http://localhost:4000');
socket.on('update', function (users){
      this.userList = users;
      console.log(this.userList)
}) 
import VueJwtDecode from "vue-jwt-decode";
export default {
  
  name: 'app',
  props: {
    userList2: {
      type: Array,
      default: () => []
    }
  },
  data() {
    
    return {
      user: {},
      componentKey: 0,
      userList: this.userList2,
      
    };
  },
  
  created () {
    // get socket somehow
    socket.on('update', function (users){
      console.log(this.userList)
      this.userList = users;
      console.log(this.userList)
    }) 
  },
  methods: {
    async getUserDetails() {
      let token = localStorage.getItem("jwt");
      let decoded = VueJwtDecode.decode(token);
      let response = await this.$http.post("/update", decoded);
      let urank = response.data.rank;
      this.user = decoded;
      this.user.rank = urank;
    },
    logUserOut() {
      localStorage.removeItem("jwt");
      this.$router.push("/");
    },
    
  },
}
</script>

How can I make the Vue bootstrap group item auto-update once a change happens with socket.io?

FluxedScript
  • 2,089
  • 2
  • 8
  • 25

3 Answers3

2

Overview:

You could $emit event with an updated list back to the parent and then update the data property in the parent component. That is because you should not directly modify props.


Example:

In your child component:

import io from 'socket.io-client';

data() {
 return {
  socket: io()
 }
},
props: {
 userList2: {
  type: Array,
  default: () => []
 }
},
created() {
  this.socket.on('update', (users) => {
   this.$emit('updateListEv', users);
  })
}

then in your parent component:

<childNameComponent @updateListEv="updateList"></childNameComponent>

then you need a method in your parent component to actually update the data property with the data passed back from the child component.

methods: {
  updateList(updatedList) {
   this.userList2 = updatedList
  }
}

Note:

You should be able to use your prop directly if you do it that way so there is no need for setting additional data property in your child component - userList: this.userList2.

You would be looping through userList2 in this case - the same way that you are doing right now - v-for="value in userList2"

You can also see that in this case, we initialise the socket as a data property so that we could use it in our Vue instance.


Edit:

import io from 'socket.io-client';
data() {
  return {
    socket: io(),
    usersList: this.userList2
  }
},
props: {
 userList2: {
  type: Array,
  default: () => []
 }
},
created() {
  this.socket.on('update', (users) => {
   this.userList = users
  })
}

in your HTML template loop through the user list:

v-for="value in usersList"

Using socket.io with Vue.js and Node.js full example:

In your backend (Node.js):

//setting up sockets
const app = express()
const server = http.createServer(app)
const io = require('socket.io')(server)
io.on('connection', (socket) => {
  socket.on('sendUpdateList', function(data) {
    io.emit('listUpdate', data)
  });
})

Sending an update from the component:

import io from 'socket.io-client';

data() {
 return {
  socket: io(),
  usersList: []
 }
},
methods: {
 this.socket.emit('sendUpdateList', {usersList: this.usersList})
}

Listening to socket in the component:

import io from 'socket.io-client';

data() {
 return {
  socket: io(),
  usersList: []
 }
},
created() {
  this.socket.on('listUpdate', (data) => {
   this.usersList = data.usersList
  })
}
Jakub A Suplicki
  • 3,185
  • 1
  • 13
  • 24
1

It is a bit hard to say since we can't run your code, but having a quick look at it I think it is as simple as this:

Replace the userList2 in v-for="value in userList2" to userList.

If you are going to work more with sockets and Vue, I think the vue-socket.io is quite a useful library: https://www.npmjs.com/package/vue-socket.io

JayL
  • 530
  • 4
  • 17
0

The this inside the callback of socket.on('update' is not what you think it is (it is not the vue component), so assigning to this.userList won't trigger any changes in the vue component).

Use and arrow function as the callback so that it uses the surrounding this (which is your component) like so:

import io from 'socket.io-client';
import $ from 'jquery'
import VueJwtDecode from "vue-jwt-decode";

var socket = io('http://localhost:4000');

export default {
  name: 'app',
  props: {
    userList2: {
      type: Array,
      default: () => []
    }
  },
  data() {
    
    return {
      user: {},
      componentKey: 0,
      userList: this.userList2,
      
    };
  },
  
  created () {
    socket.on('update', users => {      // arrow function here so 'this' keyword inside will refer to your vue component
      this.userList = users;            // either this one
      this.userList2 = users;           // or this one (I don't know why you are using two props for this btw)
    }) 
  },
  // ...
}

Read more about this and why arrow functions don't have them in this other SO question: How does the "this" keyword work?

ibrahim mahrir
  • 28,583
  • 5
  • 34
  • 61