How to make Global State in vue 3?

Viewed 2838

I have created a reactive value but I want it to change from the entire application.

To create the reactive state I have src/store/index.js that contains

// ** src/store/index.js

export function useTimer() {
  const timer = ref(false);

  const toggleTimer = () => {
    if (timer.value == false) {
      timer.value = true;
    } else {
      timer.value = false;
    }
  };

  return { timer, toggleTimer };
}

And I am using these values in components

// ** src/app.vue 

<template>
  <Scores/>
  <button @click="switchtimer()">switch at app</button>
</template>

<script>
import { isRef } from "vue";
import { useTimer } from "./store";
import Scores from "./components/Scores.vue";

export default {
  components: {
    Scores
  },
  setup() {

    const { timer, toggleTimer } = useTimer();

    function switchtimer() {
      console.log(isRef(timer));    // true
      toggleTimer();

    }
    return { switchtimer };
  },
};
</script>
// ** src/components/Scores.vue

<template>
  <button @click="switchtimer()">switch at scores</button>
  <p>{{ timer }}</p>
</template>

<script>
import { isRef } from "vue";
import { useTimer } from "../store";

export default {
  name: "Scores",
  setup() {

    const { timer, toggleTimer } = useTimer();

    function switchtimer() {
      console.log(isRef(timer));    // true
      toggleTimer();

    }
    return { timer, switchtimer };
  },
};
</script>

Scores.vue is imported in app.vue as component. There is no importing issue in the app even if there is any mistakes I did in this description. That mistake might have occurred during rewriting while posting this.

When switch at scores button pressed the timer toggles in scores component, but switch at app button pressed the timer does not toggles in scores component.

I want to have timer as global state, so that when changed it from app.vue, the changed value also shown in scores.vue.

2 Answers

The fix is very simple, you need to make the ref variable persistent

// ** src/store/index.js

const timer = ref(false); // <=== outside of function scope

export function useTimer() {
  const toggleTimer = () => {
    if (timer.value == false) {
      timer.value = true;
    } else {
      timer.value = false;
    }
  };

  return { timer, toggleTimer };
}

By taking it out of the function scope, you will no longer create a new timer variable every time you call the function, but rather reference the same one, like a singleton.

This seems like a perfect fit for dependency injection. Since there will be absolutely one instance of the timer state in the whole app, it's good to delegate the responsibility of "updating the single state(s)" to the service provider (in this case, the App.vue) and have the handler inject-ed by child components.

Your approach and Daniel's will work — and this is probably debatable to the extent of whether or not to have singletons actually coupled to factory functions, but I would resort to DI for this particular case.

App.vue (parent)

import { defineComponent, provide, ref } from 'vue';

export default defineComponent({
  setup() {
    const timer = ref(false);

    const toggleTimer = () => {
      timer.value = !timer.value;
    }

    provide('toggleTimer', toggleTimer);

    return { toggleTimer }
  }
})

Scores.vue (child)

import { defineComponent, inject } from 'vue';

export default defineComponent({
  name: 'Home',

  setup() {
    const toggleTimer = inject('toggleTimer');

    return {
      toggleTimer
    }
  }
})
Related