2

At the time I was writing this question I found the solution to my problem, but even so I decided to share it with the community to see if I'm solving the problem in the best way possible.

Given a summary of my Store:

//  store/index.js

const store = createStore({
  state: {
    userBooks: [],
  }
  mutations: {
    setUserBooks(state, val) {
      state.userBooks.push(val);
    },
  actions: {
    addBook({ commit }, payload) {
      commit("setUserBooks", payload);
  }
})

I'm calling the action like this:

//  Add.vue

methods: {
  addBook(book) {
    this.$store.dispatch("addBook", book);
  },
}

This was giving me the following error:

Uncaught (in promise) TypeError: state.userBooks.push is not a function
  • books is an object obtained through a v-for and contains properties like id, title, author, thumbnail, and ISBN.

I had already checked this solution: Push to vuex store array not working in VueJS. And that's exactly what I tried, but I got the above error.

How I solved the problem:

I noticed that the book object was coming into the function as a proxy object. With that in mind, I turned the proxy object into a regular object as follows:

addBook(book) {
  book = Object.assign({}, book);
  this.$store.dispatch("addBook", book);
}

Why does the problem happen?

I confess that I still don't understand why the problem occurs. book is obtained via the v-for of books. books is assembled from a Google Books API query. The query is done using axios.get().then()

The console.log(this.books) already returns me a proxy object and I confess that I don't know if this is the expected behavior and if I should try to change it.

Anyway the problem is solved, but if anyone has any different approach I would be very happy to learn something new.


EDIT: More code

I decided to edit the question to show how books are generated and populated.

<template>
    <figure v-for="(book, index) in books" :key="index">
      <Button text="+" @click="addBook(book)" />
      <!-- I omitted the use of the other elements to make things more objective. -->
    </figure>
</template>

<script>
export default {
  data() {
    return {
      books: {},
    };
  },
  methods: {
    search() {
      axios
        .get(`https://www.googleapis.com/books/v1/volumes?q=${this.seek}`)
        .then((response) => {
          this.books = response.data.items.map((item) => ({
            id: item.id,
            title: item.volumeInfo.title,
            authors: item.volumeInfo.authors || [this.$t("book.unknown-author")],
            ISBN: item.volumeInfo.industryIdentifiers?.[0].identifier ?? item.id,
            thumbnail: item.volumeInfo.imageLinks?.thumbnail ?? this.noCover,
          }));
        })
        .catch((error) => console.error(error))
    },
    addBook(book) {
      // Object.assign({}, book)
      book = { ...book };
      this.$store.dispatch("addBook", book);
    },
  },
};
</script>
ARNON
  • 1,097
  • 1
  • 15
  • 33
  • 2
    I originally misinterpreted the problem. Unless you have some unlikely condition on your side that userBooks reactive array from working as an array, this means that userBooks was replaced with something that is not an array - likely an object. The question doesn't contain this. I don't see how the problem can be reproduced with the code you posted only and how `book = Object.assign({}, book)` can fix "state.userBooks.push is not a function" error. Consider providing a way to reproduce it. `} mutations` - this is syntax error, this suggests that the code differs in some way – Estus Flask Dec 27 '21 at 09:33
  • @EstusFlask I just edited my question including more code. Now we can see how `books` are generated and populated. – ARNON Dec 27 '21 at 09:54
  • 1
    `books: {}` -this. If it's expected to be an array, it should be an array. But I see no direct connection with the error you have. If the problem persists, consider providing a demo that can reproduce it. I'd expect to see `userBooks = someObject` in some part of the store, otherwise it cannot be nothing but an array and so it should have `push` method. You can also debug and check what exactly userBooks is when the error occurs – Estus Flask Dec 27 '21 at 13:46

1 Answers1

1

Another new faster way is spread operator. You create a new object and spred variables inside book object. It works same as book = Object.assign({}, book)

book = { ...book }

Bellow more examples of usage of spred operator:

  • You can use it in arrays to if val is an array with and object if not just don't type ... before val.
setUserBooks(state, val) {
      state.userBooks = [...state.userBooks, ...val];
}
  • Or for example you have big object called user and you have in this object his address object and he wants to change it.
setUser(state, address) {
      state.user = {...state.user, address};
}
Mises
  • 4,251
  • 2
  • 19
  • 32
  • 1
    Vue state isn't needed to be immutable. The problem here is that userBooks is not an array. – Estus Flask Dec 27 '21 at 06:11
  • Your first solution works fine; The second one returns `Uncaught (in promise) TypeError: Invalid attempt to spread non-iterable instance. In order to be iterable, non-array objects must have a [Symbol.iterator]() method.`; About your third option, I prefer not to do it because my user object is used only for authentication. Anyway, I upvoted you because that spread operator frees me from `Object.assign()`. – ARNON Dec 27 '21 at 09:32
  • 1
    @Arnon The first option is an alternative solution to your problem it works same as 'book = Object.assign({}, book)'. Second and third is only an example what you can do with spread operator. – Mises Dec 27 '21 at 15:07