Skip to content

UTable #actions-data outdated when rows change #2004

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
robert-gruner opened this issue Aug 1, 2024 · 9 comments · Fixed by #2600
Closed

UTable #actions-data outdated when rows change #2004

robert-gruner opened this issue Aug 1, 2024 · 9 comments · Fixed by #2600
Labels
bug Something isn't working

Comments

@robert-gruner
Copy link

robert-gruner commented Aug 1, 2024

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

@ldiellyoungl
Copy link

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>

@robert-gruner
Copy link
Author

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?

@ldiellyoungl
Copy link

ldiellyoungl commented Aug 5, 2024

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

@robert-gruner
Copy link
Author

@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").

@ldiellyoungl
Copy link

hmmm... maybe i dont understand, sorry. In my production i use websockets and change array with forEach method refresh method not my chose :/

@robert-gruner
Copy link
Author

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.

@ldiellyoungl
Copy link

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:
image

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)
}

@benjamincanac benjamincanac added the bug Something isn't working label Aug 7, 2024
@oozcitak
Copy link

oozcitak commented Sep 5, 2024

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

@coreyshuman
Copy link

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants