GIVESocialMovement / sbt-vuefy

Use Vue.js with Playframework

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Components lose reactivity with sbt-vuefy

nirslsk opened this issue · comments

Thanks so much for making this! I'm very excited about the possibility of using sbt-vuefy to create single-page components instead of having them in template files. It took me a while to figure out how to set it up. The library dependency for sbt-vuefy doesn't seem to resolve on Play 2.5 so I had to upgrade to Play 2.6, and since I'm completely unfamiliar with npm and webpack, I didn't realize I was supposed to copy package.json from the test project to my own project and run npm install.

I only have one (recursive) Vue component right now for displaying threaded comments, and it works great in the template, but when I move it to a .vue file, even though it compiles just fine and the comment tree displays initially, a lot of the reactivity is gone. In link-comment.vue, I define these props:

        props: {
            comment: { type: Object, required: true },
            commentArray: { type: Array, required: true },
        }

And instantiate thusly:

        <link-comment v-for="childComment in comment.replies"
                      :key="childComment.commentId"
                      :comment="childComment"
                      :comment-array="comment.replies"></link-comment>

All of the reactive data is really in the comment prop, which is an object that has various attributes, example json:

{
   commentId: 368,
   userName: "Kirk Avocado",
   commentText: "The universe was a mistake.",
   likeCount: 1,
   liked: false,
   ownComment: true,
   replies: [...]
}

These two methods for example, change comment.liked, comment.likeCount, and the comment.replies array, and I can verify in the browser debugger that the object data changes and stays changed, but none of the changes are rendered:

toggleLike: function() {
                let comment = this.comment;

                $.ajax({
                    method: 'POST',
                    url: comment.liked ? '/api/unlike_link_comment' : '/api/like_link_comment',
                    contentType: "application/json; charset=utf-8",
                    data: JSON.stringify({ commentId: comment.commentId }),
                    dataType: 'json',
                    success: function (response) {
                        if (comment.liked) {
                            comment.liked = false;
                            comment.likeCount--;
                        } else {
                            comment.liked = true;
                            comment.likeCount++;
                        }
                    },
                    error: function (req, status, error) {
                    }
                });
            },
            deleteComment: function() {
                let data = this;

                if (confirm('Delete your comment?')) {
                    $.ajax({
                        method: 'POST',
                        url: '/api/delete_link_comment',
                        contentType: "application/json; charset=utf-8",
                        data: JSON.stringify({
                            commentId: data.comment.commentId
                        }),
                        dataType: 'json',
                        success: function () {
                            data.commentArray.splice(data.commentArray.indexOf(data.comment), 1);
                        },
                        error: function (req, status, error) {
                        }
                    });
                }
            }

Weirdly other changes like editing a comment (which changes comment.commentText) or adding a reply (which modifies the comment.replies array) do render. There's no rhyme or reason to it.

I realize this seems more like a VueJS problem, but it all works when the components are in the template, so I'm wondering is something is lost along the way when the .vue file is compiled that breaks reactivity...

I'm happy to send you the entire .vue file and complete JSON data to initialize it with if you deem it necessary.

Thanks, Nir

Yeah, that's strange. I remember a somewhat similar problem which seems to be related to the fact that we cannot change props (or, technically, it gets overwritten).

I wonder if you could try:

props: {
  comment: { type: Object, required: true },
  commentArray: { type: Array, required: true },
},
data: function() {
  return {
    mutableComment: this.comment
  };
}

// and use mutableComment every where instead.

Let's keep commentArray the same for now to see if this fixes the problem with comment.liked and comment.likeCount.

Thank you for trying sbt-vuefy and for the feedback. I'll improve the README accordingly.

Also, if you can put the code in the repo and share it with me, that would be great. Please let me know if using data works.

Thank you Tanin, the mutableComment thing totally fixed it. You don't event need to use it directly, it looks like Vue starts monitoring the comment object once it's defined as data, and since comment and mutableComment point to the same object you can use comment directly. Weirdly, this also made changes to commentArray render, even though I didn't add a mutable version of it as well.

I know props get overwritten with each render (as per Vue documentation), but I was under the impression you could change the contents of a prop object, just not the object itself. Anyway, I'll make sure to put everything in data that I intend to make changes to, and chalk up the fact that it works when the component is embedded in a Twirl template to general Vue quirkiness.

I'm assuming you don't need the component code anymore, but just in case, you can find it along with sample json data here: https://gist.github.com/nirslsk/5d5be790d5674f0a7b522ef00036b27c

Weirdly, this also made changes to commentArray render, even though I didn't add a mutable version of it as well.

That baffles me. Internally, Vue might distinguish between mutable components (with data) and immutable components (without data).

I know props get overwritten with each render (as per Vue documentation), but I was under the impression you could change the contents of a prop object, just not the object itself.

My best guess is that it might have something to do with the way we set props in a Twirl template.

If we set comment: {...}, then it might get replaced. But if we do:

let comment = {...}
...
  comment: comment
...

Then, the value might not be replaced.

Not to belabor the point, I settled on a pretty elegant convention for monitoring props:

data: function() {
            return {
                showEditForm: false,
                showReplyForm: false,
                replyFormCommentText: '',
                editFormCommentText: this.comment.commentText,
                monitor: {
                    comment: this.comment,
                    commentArray: this.commentArray
                }
            };
        },

Here I put this.comment and this.commentArray in a monitor object, and then I can use this.comment and this.commentArray directly. It might not be intended usage but so far no problems.

Thanks again for this great plugin!

You're welcome. I'm glad it is working for you.