Reputation: 2949
I have a search field and I want to filter users based on it.
I show users if there is no filter or filteredUsers
if you search anything.
The issue is that if you search in the search bar and after that I'm trying to edit a field (inline edit), users
is updated, but filteredUsers
is not.
I have also other filters and actions, that's why I've posted only that part of the code. I have to use both of them because in other cases I need users.
How should this work in vue.js
?
//in componnet
computed: {
...mapGetters([
'users',
'filteredUsers'
]),
getFilteredUsers () {
return this.filteredUsers || this.users
}
}
// in mutation
UPDATE_CONSUMER (state, { user, index }) {
// here users is updated, but filteredUsers in not
state.users.splice(index, 1, user)
},
<tr v-for="user, index in getFilteredUsers">
// getters.js file
export default {
users: state => {
return state.users
},
filteredUsers: state => {
return state.filteredUsers
}
}
// mutations.js
SEARCH_USERS (state) {
const searchTextTrimmed = state.searchText.trimStart()
const users = [
...state.users.filter(user => {
return user.name.toLowerCase().startsWith(searchTextTrimmed.toLowerCase())
})
]
if (searchTextTrimmed) {
state.filteredUser = users
} else {
state.users = users
state.filteredUser = null
}
}
Upvotes: 0
Views: 190
Reputation: 29092
Ideally you wouldn't have both arrays in the state
. Instead you'd just have users
and searchText
. You'd then have filteredUsers
in the getters
, derived from users
and searchText
.
In the example below I've tried to preserve the UPDATE_CONSUMER
mutation from the question. I extrapolated from there to assume that you want to create copies of the user objects when they are edited rather than mutating the original objects. The whole question becomes a bit moot if mutating the original objects is an option as the objects are shared between the two lists.
const store = new Vuex.Store({
state: {
searchText: '',
users: [
{name: 'Black', fruit: 'Apple'},
{name: 'Blue', fruit: 'Pear'},
{name: 'Brown', fruit: 'Banana'}
]
},
mutations: {
UPDATE_CONSUMER (state, { user, index }) {
state.users.splice(index, 1, user)
},
UPDATE_SEARCH_TEXT (state, searchText) {
state.searchText = searchText
}
},
getters: {
filteredUsers (state) {
const searchText = state.searchText.trimStart().toLowerCase()
const users = state.users
if (!searchText) {
return users
}
return users.filter(user => user.name.toLowerCase().startsWith(searchText))
}
}
})
new Vue({
el: '#app',
store,
computed: {
...Vuex.mapState(['users']),
...Vuex.mapGetters(['filteredUsers']),
searchText: {
get () {
return this.$store.state.searchText
},
set (searchText) {
this.$store.commit('UPDATE_SEARCH_TEXT', searchText)
}
}
},
methods: {
onInputFruit (user, fruit) {
const newUser = {...user, fruit}
const index = this.users.indexOf(user)
this.$store.commit('UPDATE_CONSUMER', {user: newUser, index})
}
}
})
#app > * {
margin: 0 0 10px;
}
table {
border-collapse: collapse;
}
td, th {
background: #eee;
border: 1px solid #777;
padding: 5px;
}
<script src="https://unpkg.com/[email protected]/dist/vue.js"></script>
<script src="https://unpkg.com/[email protected]/dist/vuex.js"></script>
<div id="app">
<input v-model="searchText">
<table>
<tr><th>Name</th><th>Fruit</th><tr>
<tr v-for="user in filteredUsers" :key="user.name">
<td>{{ user.name }}</td>
<td>
<input :value="user.fruit" @input="onInputFruit(user, $event.target.value)">
</td>
</tr>
</table>
<p>{{ filteredUsers }}</p>
<p>{{ users }}</p>
</div>
Some other points to note:
users
and used mapState
instead. If you'd rather do everything via mapGetters
you could put that back the way it was.getFilteredUsers
. Instead just have filteredUsers
always return the current list of users matching the search criteria. I've included an optimisation to return early if there is no searchText
but it would still work without it.const users = [...state.users.filter
. There's no need to use [...]
to create a new array here as filter
will be returning a new array anyway.searchText
and filtering in the store but that feels a bit like it should be handled by component state instead. It's difficult to say without knowing a lot more about your application but the search box is likely to be local to a small portion of the UI and that doesn't necessarily need to be shared with the rest of the application via the store.A potential problem with the way I've implemented this is that the data is updated immediately as the user types. It isn't a problem in my example but if the list is filtered based on the field being edited that could cause that row to suddenly disappear as the user is typing. One way to avoid that problem is to wait for the field to blur before updating the data, e.g. using the change
event instead of the input
event. Other workarounds are available depending on precisely what the expected behaviour should be.
Upvotes: 1