A box of LEGO blocks

Caveat about destructuring Vue props

Destructuring objects in JavaScript is a really cool feature. Using it along with Vue can, however, take on a spin… Let’s dive into one caveat on the topic.

The problem

I will explain my use case: I had a PostList component on a forum application. I used this component is two places:

  • one needed the posts to be ordered in the ascending order,
  • the other needed the contrary.

While working on that, I declared my props this way:

1
2
3
4
5
6
7
8
interface PostListProps {
  posts: Post[];
  orderBy: OrderByDirection;
}

const { posts, orderBy } = withDefaults(defineProps<PostListProps>(), {
  orderBy: OrderByDirection.Asc,
});

Then, I used a computed to order the posts as needed:

1
2
3
4
5
6
7
8
const orderedPosts = computed(() => {
  if (orderBy === OrderByDirection.Asc) {
    return posts;
  }
  return [...posts].sort((first, next) =>
    first.publishedAt! < next.publishedAt! ? 1 : -1
  );
});

When I went to test this code, adding a new post worked but it didn’t show in the list.

Using Vue DevTools, I saw the Pinia state updating and the parent component of PostList component did provide the full list…

The reason and solution

Why didn’t the new post appear?

Destructuring the props object broke the reactivity and computed requires a reactive dependency!

So the valid code became:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
const props = withDefaults(defineProps<PostListProps>(), {
  orderBy: OrderByDirection.Asc,
});
const orderedPosts = computed(() => {
  if (props.orderBy === OrderByDirection.Asc) {
    return props.posts;
  }
  return [...props.posts].sort((first, next) =>
    first.publishedAt! < next.publishedAt! ? 1 : -1
  );
});

Using toRefs

If you insist to destructure the props, make sure to reactive them.

For that, Vue provides a nice utility: toRefs.

The code would like that:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
import { toRefs } from "vue";

const props = withDefaults(defineProps<PostListProps>(), {
  orderBy: OrderByDirection.Asc,
});

// `posts` and `orderBy` are now reactive.
const { posts, orderBy } = toRefs(props);

const orderedPosts = computed(() => {
  if (orderBy === OrderByDirection.Asc) {
    return posts;
  }
  return [...posts].sort((first, next) =>
    first.publishedAt! < next.publishedAt! ? 1 : -1
  );
});

Conclusion

Destructuring is great, but with Vue, use it carefully, especially with computed 🙂. Thank you, toRefs!

Credit: Photo by Scott McNiel on Pexels.

License GPLv3 | Terms
Built with Hugo
Theme Stack designed by Jimmy