UTable #actions-data outdated when rows change
Environment
- Operating System: Darwin
- Node Version: v20.15.1
- Nuxt Version: 3.12.3
- CLI Version: 3.12.0
- Nitro Version: 2.9.7
- Package Manager: [email protected]
Version
v2.17.0
Reproduction
https://stackblitz.com/edit/github-hpn25g
Description
We recognized that the row data which is passed to "actions" does not reflect the state of the row data after the items array is replaced. This can happen when the whole list of items is refetched from a backend. It is relevant that the actions data is in sync with the rendered data since the actions themselves could also lead to other backend requests.
Repro:
- Load the page with the table (posts are fetched)
- Rename a single post via Rename action (modal input and confirm)
- Posts are rerendered, table properly reflects new name for one of the posts
- Reopen the Rename modal
- See there is still the former name present, there was also a log statement showing that old model data is passed
- Expectation would be that the table component always forwards the current data to the actions
I am aware that I have to "fake" the refetch mechanism since the used dummy API does not allow mutations on the server. In our real world scenario we have a real backend but the issue is still reproducible.
As a workaround we use a constant field of a model (e.g. "id") and grab the item from the refetched lists of models before we pass it to the modal.
Is this a real bug or are we doing something wrong here? If you could give me some hints where/how to start I could try to provide a PR with a fix.
Additional context
No response
Logs
No response
Hello. I try and its work for me
<script setup>
import { PostsModal } from '#components';
const modal = useModal()
const posts = ref([
{
id: 1,
title: 'Title1'
},
{
id: 2,
title: 'Title2'
},
{
id: 3,
title: 'Title3'
}
])
const columns = [
{
key: 'id',
label: 'ID',
},
{
key: 'title',
label: 'Title',
},
{
key: 'actions',
label: 'Actions',
},
];
function rename(post) {
console.log(post)
modal.open(PostsModal, {
id: post.id,
oldTitle: post.title,
onConfirmRename(newTitle) {
console.log(newTitle)
posts.value.forEach(elem => {
if (elem.id === post.id) {
return elem.title = newTitle
}
});
modal.close()
},
onCancel() {
modal.close()
}
})
}
</script>
<template>
<UTable :columns="columns" :rows="posts">
<template #actions-data="{ row }">
<UButton title="Rename" color="primary" @click="rename(row)">Rename</UButton>
</template>
</UTable>
</template>
<script setup>
const emit = defineEmits(['confirmRename', 'cancel'])
const props = defineProps({
id: Number,
oldTitle: String,
})
const newTitle = ref(props.oldTitle)
</script>
<template>
<UModal>
<UCard>
<p>
New name for post with title <span> {{ oldTitle }} </span> is:
</p>
<input v-model="newTitle" placeholder="New title" />
<template #footer>
<div>
<UButton variant="ghost" @click="emit('cancel')">Cancel</UButton>
<UButton color="primary" @click="emit('confirmRename', newTitle)">Rename</UButton>
</div>
</template>
</UCard>
</UModal>
</template>
True. I forked a Stackblitz with your code: https://stackblitz.com/edit/github-hpn25g-femxdi Yes it is working. Played around a bit and the reason/difference is, that you manipulate the data array (posts) in place instead of replacing the whole array. That is interesting, but not really a solution since in a real-world scenario we use the "refresh" function of AsyncData. So why does the table component have a problem with replacing the whole data array?
this works fine https://stackblitz.com/edit/github-hpn25g-oehbpx
i change this func
async function postsRefresh(updatedPost) {
posts.value = posts.value.map((item) => {
if (item.id === updatedPost.id) {
return updatedPost;
}
return item;
});
}
to this
function postsRefresh(updatedPost) {
posts.value.forEach((post) => {
if (updatedPost.id === post.id) {
post.title = updatedPost.title
}
})
}
maybe problem in your composable func updatePost. She return object with keys: id, title. But your posts is array with objects keys: userId, id, title. i dont like map method, but with forEach its works fine
@ldiellyoungl I already got the difference when you answered the first time. It is not a fix to the problem since you are talking about code which we do not have in production scenario. The StackBlitz is only for demonstration purposes. In reality we do not have such a postsRefresh method. In reality we refetch the whole list again from backend. Then the list of models is replaced completely (so .map is actually more realistic than .foreach).
The real bug is that after replacing the data array the table does reflect the new model properties, so the rendering is correct. But what is not correct is the model data which is passed to actions ("#actions-data").
hmmm... maybe i dont understand, sorry. In my production i use websockets and change array with forEach method refresh method not my chose :/
No worries. Using foreach and keeping the existing items list is also some kind of workaround for this problem described here (could be problematic if there are new or removed items from the list though?). We work around the issue like initially mentioned: we just ignore the model data which goes into #actions-data and find the model by id from the data array which is always uptodate.
I experement with your code
async function postsRefresh(updatedPost) {
posts.value = posts.value.map((item) => {
if (item.id === updatedPost.id) {
return updatedPost;
} else {
return item;
}
});
console.log(posts.value)
}
and got this:
first object is not proxy. maybe this is problem? i try this code and its work! Can you check this? Most likely I did the same as above....
async function postsRefresh(updatedPost) {
posts.value = posts.value.map((item) => {
if (item.id === updatedPost.id) {
return Object.assign(item, updatedPost);
} else {
return item;
}
});
console.log(posts.value)
}
I have the same problem. #<column>-data slot's row property is not updated after refreshing table data. As a workaround similar to @robert-gruner 's I use the index property of the slot to lookup the actual row data.
As a side note index slot property is not documented here: https://ui.nuxt.com/components/table#column-data
I'm troubleshooting a problem caused by the PR for this issue and wanted to share a discovery in case someone else views this discussion. The refresh issue ironically has nothing to do with the table and is instead a side effect of the <UButtonGroup> component. I modified the OP's reproduction to demonstrate:
https://stackblitz.com/edit/github-hpn25g-uhp6qdgd?file=app.vue
I added a second button within the actions template but outside of <UButtonGroup> component. Notice that the first button will show stale data in its tooltip and modal when clicked, but the second button will be show the latest data.
This demonstrates that the table and row variable are not at fault here.