jeffreyguenther / vue-turbolinks

A Vue mixin to fix Turbolinks caching
MIT License
287 stars 20 forks source link

VueJS application doesn't work after browser "Back" function. #7

Closed Shoroh closed 7 years ago

Shoroh commented 7 years ago

Hey Jeffrey! Thank you for the solution, it was a nice idea.

But something is still wrong, could you please take a look? I'm not sure that this is related to the Turbolinks or your solution, it might be a VueJS bug, but anyway :)

I created a really simple form using rails-slim template:

.row.justify-content-center
  .col-sm-4
    h2 Create a new account
    = bootstrap_form_for @account do |f|
      = f.text_field :name, 'v-model': 'name'
      = f.primary 'Create Blog'
= javascript_pack_tag 'hello_vue'

It gives us an input field we need to use with VueJs:

<input type="text" name="account[name]" id="account_name" class="form-control">

And typical VueJs App:

import Vue from 'vue/dist/vue.esm'
import TurbolinksAdapter from 'vue-turbolinks'

document.addEventListener('turbolinks:load', () => {
  const element = document.getElementById('new_account')
  if (element != null) {
    const accountName = new Vue({
      el: element,
      data: {
        name: '362'
      },
      mixins: [TurbolinksAdapter]
    })
  }
});

And it works well when I went to the page (two way binding, and so on, I'm able to see it in console) Until I go to another page and come back twice :)

Look: may-28-2017 19-50-14

Here you can see the initial state when the page is just opened. Then I got to another page and use browser "Back" button to return to the page with the New Account Form. When I did it the first time you still see 362 in the input field, but the VueJs App is already stopped and doesn't work. When I returned the second time you can see that the field is empty at all :)

What do you think about it? Did I miss something?

Shoroh commented 7 years ago

I think the issue is in existed form. VueJS removes all directives from the form, like v-model, @input, etc, after it was activated. Thus 'before:cache' event stores the form without directives.

Since this time the next time when VueJS App will be activated, there won't be any directives in the form. And it won't be working.

Let me know what you think, please.

jeffreyguenther commented 7 years ago

Interesting! I think you're on the right track.

What we need to do is figure out if the right HTML is cached in the beforeMount callback. We might need to move the cache to earlier in the lifecycle of the component.

jeffreyguenther commented 7 years ago

And for debugging efficiency, are you using the latest version of the mixin?

Shoroh commented 7 years ago

Thanks, I thought the same. And I have no idea how to deal with it at the moment :)

Yep, I use the latest version, which is 1.02 I believe.

jeffreyguenther commented 7 years ago

I recently received a reply on the VueJS forum from someone who has done something similar to Chris and my mixin. You can read about it: https://forum.vuejs.org/t/using-vue-alongside-turbolinks-in-rails-5-1/9084/3

This difference between his approach and ours is that he caches the element state. Indirectly, I think this might be what you're seeing. I need to do some experiments to find out for sure.

Shoroh commented 7 years ago

Thanks for the link.

Unfortunately, his solution still works good only when we use templates in an application.

tatey commented 7 years ago

What we need to do is figure out if the right HTML is cached in the beforeMount callback. We might need to move the cache to earlier in the lifecycle of the component.

I think the issue is that this plugin is tearing down a DOM that is discarded during the page transition.

  1. Page initialises and components get bootstrapped
  2. The user initiates a visit to a new page. Turbolinks snapshots the page, this plugin tears down the component from the DOM, and the transition completes.
  3. When the user requests to go back, Turbolinks restores the previous snapshot. The snapshot happened before the teardown, so the work of the teardown is effectively discarded.

All we need to do is switch to using the turbolinks:before-cache event :tada:

export default {
  beforeMount: function() {
    if (this !== this.$root) return;

    this.__TurbolinksAdapterOriginalOuterHTML__ = this.$el.outerHTML;
    var _this = this;
    document.addEventListener("turbolinks:before-cache", function teardown() {
      _this.$destroy();
      document.removeEventListener("turbolinks:before-cache", teardown);
    });
  },

  destroyed: function() {
    if (!this.__TurbolinksAdapterOriginalOuterHTML__) return;

    this.$el.outerHTML = this.__TurbolinksAdapterOriginalOuterHTML__;
    delete this.__TurbolinksAdapterOriginalOuterHTML__;
  },
};

If you think this is useful I'd be happy to contribute a patch.

I recently received a reply on the VueJS forum from someone who has done something similar to Chris and my mixin. You can read about it: https://forum.vuejs.org/t/using-vue-alongside-turbolinks-in-rails-5-1/9084/3

This feels like it might be a superior approach, but I worry that it's more tightly coupled to the internals of both Vue and Turbolinks making it more vulnerable to API changes over time.

jeffreyguenther commented 7 years ago

@tatey We previously used turbolinks:before-cache event. We recently just changed the code to use turbolinks:before-render. 4d45fe63fd shows you what things used to look like.

In #8, a change was made to move the snapshot earlier in the Turbolinks event sequence to account for when Turbolinks caching is turned off. It looks to me like these problems are related.

If you can take both cases into account with your PR, we'd be more than happy to give it a review. 😄

jeffreyguenther commented 7 years ago

Based on my tests this morning, I think that handling tear down inturbolinks:visit will solve the issue.

jeffreyguenther commented 7 years ago

Fixed in 1.1.1. Update the package and you should be good to go.

ZAKRUTKA commented 3 years ago

meta name= "turbolinks-cache-control" content= "no-cache" in layout this helped me