Search code examples
javascriptvue.jsvuexvue-reactivity

Vuex getter filteredUsers not reactiv in dom but in console


having a weird issue here. maybe its because i did't understood quite right how getters work.

i try to update a list of users which i fetch from jsonplaceholder. the update of the list should trigger when i search for a user in an input field.

quick gif for visualitation:

no dom update in getter

this is my component:

<template>
  <v-app>
    <div class="mt-2 ml-2">
      <v-btn
        color="info"
        x-small
        @click="fetchUsers()"
      >
        Reset All
      </v-btn>
    </div>
    <add-user />
    <v-text-field
      v-model="search"
      class="mt-5 px-3"
      label="Search"
      required
      dense
      style="width: 300px"
      @input="filteredUsers(search)"
    />
    <ul
      class="mt-1 px-3"
      style="list-style: none"
    >
      <li
        v-for="(user, userIndex) in users"
        :key="user.id"
      >
        <ul
          class="pl-0"
          style="list-style: none"
        >
          <li
            v-for="(value, key, index) in user"
            :key="index"
          >
            <b>{{ key }}:</b> {{ value }}
          </li>
          <v-btn
            x-small
            color="error"
            class="my-2"
            @click="deleteUser(userIndex)"
          >
            Delete
          </v-btn>
        </ul>
        <hr>
      </li>
    </ul>
  </v-app>
</template>

<script>
import { mapGetters, mapActions } from 'vuex';
import AddUser from '@/components/AddUser';
export default {
  name: 'App',
  components: {
    AddUser,
  },
  data: () => ({
    search: '',
  }),
  computed: {
    ...mapGetters(['userWithId', 'users', 'filteredUsers']),
  },
  created() {
    this.fetchUsers();
  },
  methods: {
    ...mapActions(['deleteUser', 'fetchUsers']),
  },
};
</script>

and this is my store:

import Vue from 'vue'
import Vuex from 'vuex'
import axios from 'axios'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    users: [],
  },
  getters: {
    users: (state) => {
      return state.users
    },
    userWithId: (state) => (id) => {
      return state.users.filter((user) => user.id === id)
    },
    filteredUsers: (state, getters) => (search) => {
      // CONSOLE
      console.log(
        getters.users.filter((user) =>
          user.name.toLowerCase().includes(search.toLowerCase())
        )
      )
      // SHOULD DOM UPDATE
      return getters.users.filter((user) =>
        user.name.toLowerCase().includes(search.toLowerCase())
      )
    },
  },
  mutations: {
    loadUsers(state, payload) {
      state.users = payload
    },
    deleteUser(state, id) {
      state.users.splice(id, 1)
    },
    addUser(state, payload) {
      state.users.push(payload)
    },
    deleteAllExceptOne(state, id) {
      return state.users.filter((user) => user.id === id)
    },
  },
  actions: {
    fetchUsers(context) {
      axios
        .get('https://jsonplaceholder.typicode.com/users')
        .then((res) => {
          const users = res.data
          users.forEach((user) => {
            delete user.address
            delete user.company
            delete user.phone
            delete user.website
          })
          context.commit('loadUsers', users)
        })
        .catch((err) => {
          console.log(err)
        })
    },
    deleteUser(context, userId) {
      axios
        .delete(`https://jsonplaceholder.typicode.com/users/${userId}`)
        .then(() => {
          context.commit('deleteUser', userId)
        })
        .catch((err) => {
          console.log(err)
        })
    },
    addUser(context, user) {
      axios
        .post('https://jsonplaceholder.typicode.com/users')
        .then((res) => {
          user.id = res.data.id
          context.commit('addUser', user)
        })
        .catch((err) => {
          console.log(err)
        })
    },
  },
  modules: {},
})

Solution

  • If I understand correctly, you want to display the filtered user list below the search input, right?

    It seems the error is that you're iterating over users instead of filteredUsers to create the list.

    Perhaps, this would work for you:

    <template>
      <v-app>
        ...
        <v-text-field
          v-model="search"
          class="mt-5 px-3"
          label="Search"
          required
          dense
          style="width: 300px"
        />
        <ul
          class="mt-1 px-3"
          style="list-style: none"
        >
          <li
            v-for="(user, userIndex) in filteredUsers(search)"
            :key="user.id"
          >
         ...
      </v-app>
    </template>
    

    Another suggestion would be to make filteredUsers a computed property in your component and remove it from your store. It would look like this:

    <template>
      <v-app>
        ...
        <v-text-field
          v-model="search"
          class="mt-5 px-3"
          label="Search"
          required
          dense
          style="width: 300px"
        />
        <ul
          class="mt-1 px-3"
          style="list-style: none"
        >
          <li
            v-for="(user, userIndex) in filteredUsers"
            :key="user.id"
          >
         ...
      </v-app>
    </template>
    
    <script>
    import { mapGetters, mapActions } from 'vuex';
    import AddUser from '@/components/AddUser';
    export default {
      name: 'App',
      components: {
        AddUser,
      },
      data: () => ({
        search: '',
      }),
      computed: {
        ...mapGetters(['userWithId', 'users']),
        filteredUsers () {
          return this.users.filter((user) =>
            user.name.toLowerCase().includes(this.search.toLowerCase())
          )
        }
      },
      created() {
        this.fetchUsers();
      },
      methods: {
        ...mapActions(['deleteUser', 'fetchUsers']),
      },
    };
    </script>