Reputation: 1325
I am having a rather strange problem with vue (v2.6.14) in which I am creating a new array based on one receive as a prop. Here's the relevant code:
props: { employees: Array },
data() {
return {
sortedEmployees: [],
};
},
mounted() {
this.sortedEmployees = this.employees.slice(0);
},
Essencially what I want here is to create a new reference for the employees array so that I can sort it to display later without actually altering the original array. I am not worried about cloning the actual objects inside the array since I will not alter them.
The problem with this is that, when the app 'hot reloads'(due to some change in the code) it works as expected, the hook is called and the component data is set according to what is expected BUT if I actually refresh the page in the browser, even though the hook is called, the component data is not set and I end up with an empty array. I can solve this by setting up a watcher to the prop and then it would set the data there, but what I am interested here is understanding what's happening. If the hook is called when the page is refreshed why doesn't it set the data properly as it does when 'hot reloading'?
I have a minimal setup created with vue-cli, no fancy configurations whatsoever. Any clue what I might be missing?
Upvotes: 1
Views: 3534
Reputation: 1105
It sounds like the component is Refreshing (reseting) with the prop change however it's not Re-mounting. This is what would cause the array to reset to it's default state ([]), whereas on hot-reload the actual page is reloading.
Try the following, and if it doesn't resolver the issue I would suggest going the route of using a Watcher.
<template>
<div>
sorted: {{ sortedEmployees }}
<br />
not sorted: {{ employees }}
</div>
</template>
<script>
export default {
props: {
employees: Array,
},
data() {
return {
sortedEmployees: Array.from(this.employees),
};
}
};
</script>
Another approach is to just use a Computed method so and add any filtering/sorting logic inside that method. It would be something like:
<template>
<div>
<input v-model="sortKey" />
sorted: {{ sortedEmployees }}
</div>
</template>
<script>
export default {
props: {
employees: Array,
},
data () {
return {
sortKey: ''
}
},
computed: {
sortedEmployees() {
return Array.from(this.employees).sort(this.sortingFunction);
},
},
methods: {
sortingFunction(a, b) {
// sorting function using this.sortKey
}
}
};
</script>
UPDATED ANSWER
I switched up the code in the example a little and believe to have gotten it to work as I you're describing.
App.js First off, I made it so that the employees array is updated after 3 seconds.
<template>
<div id="app">
<Dashboard :employees="employees" />
</div>
</template>
<script>
import Dashboard from './components/Dashboard.vue';
export default {
name: 'App',
components: { Dashboard },
data () {
return {
employees: [
{
employeeId: '1',
firstName: 'Leite',
}
]
};
},
methods: {
updateEmployees () {
this.employees = this.employees.concat([
{
employeeId: '2',
firstName: 'Jacinto',
},
{
employeeId: '3',
firstName: 'Capelo',
}
]);
}
},
mounted () {
setTimeout(this.updateEmployees, 3000)
},
};
</script>
Dashboard.js
The updated()
lifecycle hook runs whenever data changes are detected (props and data properties). This effectively detected the change in the prop passed by the parent App.js component and re-rendered the data - whereas the mounted
hook only ran once per page load.
<template>
<div id="dashbord">
<div
v-for="(employee, index) in sortedEmployees"
:key="employee.employeeId"
>
{{ employee.firstName }}
</div>
</div>
</template>
<script>
export default {
name: 'Dashboard',
props: {
employees: Array
},
data() {
return {
sortedEmployees: Array.from(this.employees)
};
},
updated() {
this.sortedEmployees = Array.from(this.employees)
}
};
</script>
Upvotes: 1
Reputation: 1174
I guess the employees are loaded async, right?
I don't know your exact application structure, but the problem is usually the following:
The mounted
hook gets called, when the component mounts, of course. If the employees
are loaded async in the parent component, the mount hook is called, before the async call is resolved. So it will copy an empty array at this time.
With a watcher you solve this problem, because the watcher fires as soon as the async call resolves (as it mutates the employees
array).
Same happens to the hotreload. When the hotreload occurs, the mounted
hook gets executed again - at this time the employees
array is already prefilled with values and therefore the correct array is copied in the mount
hook.
Update
If you want to avoid watchers, you could also wrap your component like this:
<your-component
v-if="employees.length > 0"
:employees="employees"
/>
Be aware, that the copied employees
array IN your component is still not reactive. It just copies the array on the first time it has more than 1 value. A watcher really makes sense in this case.
If you use watchers, make sure to use the immediate: true
option. This ensures, that the watcher is also called the first time on render (and also on hotreload).
Summary
If you really want to copy the array, use watchers (with the immediate: true
flag).
If it's just about sorting, go for the computed property solution suggested by @Sebastian Scholl
Upvotes: 2