What's the best way to propagate data through your Vue3 App?

Viewed 487

Is there a recommended way of how to propagate data through your vue components? What I'm trying to do is get the data from backend once and propagate it everywhere in my project but I can't find the right strategy.

sessionStorage: Works great and resets on refresh/close window but as soon as you need to create target="_blank" anchor tags, it will not propagate your data to new tabs.

localStorage: Requires in my opinion more work than sessionStorage because you need to delete data manually to keep things tidy. One big problem for me is that it looks like you can't pass markdown and arrays properly, at least without stringify. I've built a project with localStorage and ended up sending ajax requests from most of my components because I couldn't propagate the data through my app how I wanted. At this point my frontend is backend.

My personal problems with localStorage: I am using the marked package to display Markdown but it throws errors if passed undefined. This gets problematic, when I want to use it in a reactive state because instead of resulting in undefined, it throws an error and crashes the whole app. The point I am trying to make is that when you pass an undefined localStorage value to marked in an either or field like so:

const state = reactive({
    value: marked(localStorage.value) || ""
})

it crashes your app, if localStorage.value is empty.

Another problem is that I fetch text content depending on a locale and store it in localStorage. This is great until the user changes locale and all content strings have to be replaced by the translated strings. It gets really tricky, if I want to use one component as template to load in different locales.

vuex: I've tried vuex shortly and found it useful but didn't see the benefit over just using localStorage for my purposes. Prolly I'll give it another go.

How do you propagate data through your app?

2 Answers

There are a few good arguments why Vuex is better than Local Storage:

https://www.quora.com/What-is-the-benefit-of-using-Vuex-over-LocalStorage-to-store-the-state-of-an-application-in-Vue-js

You can also try composables. They are reusable functions (similar to mixins) in composition-api (you need composition-api plugin in vue2, in vue3 it is built-in). It can be also the place you store your data. It can be easier and more intuitive than Vuex.

First, create directory /composables and add javascript file (it's good practice to create file beginning with use word) useState.js:

import { reactive, toRefs } from "vue";

const state = reactive({
  isMenuOpened: false
});

const toggleMenuState = () => {
  state.isMenuOpened = !state.isMenuOpened;
};

export default {
  ...toRefs(state),
  toggleMenuState
};

toRefs converts all of the properties, to a plain object with properties that are refs

Now you can use composable in vue components:

<script>
import useState from "./composables/useState";

export default {
  setup() {
    const { isMenuOpened, toggleMenuState } = useState;

    return {
      isMenuOpened,
      toggleMenuState,
    };
  },
};
</script>

Demo: https://codesandbox.io/s/happy-chandrasekhar-o05uv?file=/src/App.vue

About composition api and composables: https://v3.vuejs.org/guide/composition-api-introduction.html

Since you have mentioned local storage and session storage, I believe you must be trying to share your state across tabs/windows rather than just different components on a single page. At this scale I don't think this is necessarily a VueJS specific issue/pattern. Generally speaking, you want data to be shared pretty much across process boundaries.

Session Storage used to be one of the most sensible ways because it is shared between one window and all the child windows it has created, until all of them are closed at which point the storage will be discarded as well. However, depending on your use cases, Chrome (within the past year) made a change to NOT inherit the session storage from the original window if the popup windows is opened as noopener, hence if you are relying on the noopener (and its performance implications), session storage is no longer usable for this purpose.

Vuex does not solve this issue neither. In fact, Vuex is pretty much irrelevant here. Given the application architecture implied, the state management capability Vuex brings to your app will be likely redundant because any state mutation will probably be submitted to your backend anyway. In some sense the Vuex store is on your backend rather than your frontend.

So we typically do one of the three approaches:

  • directly broadcast from backend to all frontend tabs. E.g. there is no state sync-ing directly between frontend tabs. Every single tab (child window) communicates directly with the server: it mutates the state by submitting actions to the server, and only the server can change the state and broadcast the changes back to all the tabs in real time (again, conceptually it feels like the Vuex store is on your backend)
  • use SharedWorker. SharedWorker is shared by all the browsing context with the same origin. It is activated the moment the first browsing context (of a certain origin) is created, and is kept alive until the last browsing context is destroyed. In some sense its sharing semantic is similar to that of the old session storage. You can use SharedWorker as the single entity to communicate to your backend. States can be maintained by the SharedWorker and accessed from the tabs in a RPC fashion. Or states can be maintained separately in each tab and SharedWorker just broadcast the changes to the tabs.
  • if you actually do not have a backend, but you just want to build multi-window single page application, you can make one of your tabs special and act as the owner of the state store. For all the child windows created from this "master" window, their local store will be a proxy - the actions they perform against the local store will be proxied over to the master window; and the master window performs the action in its store, and broadcast the changes to all the child windows.

By the way, I have used the word "store" many times, but I do not necessary mean the Vuex store. The store is just a shared place where you keep your state.

Related