Computed properties from useState inside a composable is called multiple times

Hi, Im currently doing some state sharing between components with composable. From this video https://www.youtube.com/watch?v=mv0WcBABcIk, the usage is like
const useSomething = () => {
const state = useState()
...
return. {state}
}

----------
component 1
const {state} = useSomething()
const doSomething = () => {state.value = 'new state'}

component 2
const {state} = useSomething()
const useSomething = () => {
const state = useState()
...
return. {state}
}

----------
component 1
const {state} = useSomething()
const doSomething = () => {state.value = 'new state'}

component 2
const {state} = useSomething()
and the state should be shared across the 2 components. However the issue comes when I try to implement computed and watch properties inside these hooks
const useSomething = () => {
const state = useState()
const computedState = computed(() => {
const params = state.value
return fetchResult(params)
})

watch(state, () => {console.log(state)})
...
return. {state, computedState}
}

----------
component 1
const {state} = useSomething()
const doSomething = () => {state.value = 'new state'}

component 2
const {state} = useSomething()
const useSomething = () => {
const state = useState()
const computedState = computed(() => {
const params = state.value
return fetchResult(params)
})

watch(state, () => {console.log(state)})
...
return. {state, computedState}
}

----------
component 1
const {state} = useSomething()
const doSomething = () => {state.value = 'new state'}

component 2
const {state} = useSomething()
In this case, the computed and watch properties will be executed n times for every doSomething() call, where n is the number of components that have useSomething() mounted Is there a way to properly implement this behaviour without triggering computed a bunch of times?
Alexander Lichter
YouTube
Why you should use useState()
VUEJS AMSTERDAM DISCOUNT (20%): i-really-like-alexander-lichter πŸ”› Sending state from the server to the client is tricky but necessary when using SSR. And global state management is not the easiest part of an app either. Nuxt's useState composable solves both of these issues - but how? You'll learn in this weeks video! Key points: ⏩️ How to se...
No description
19 Replies
manniL
manniLβ€’7mo ago
Your computed seems to be async (fetchResults is, no?)
Shuvi ☁ πŸͺ½
Shuvi ☁ πŸͺ½OPβ€’7mo ago
Agree, async computed is probably very bad. But aside from the async function, if I were to replace it with console log it still seems to execute multiple times along side watch each time the dependency changes I can create a code sandbox to demonstrate what I meant
Shuvi ☁ πŸͺ½
Shuvi ☁ πŸͺ½OPβ€’7mo ago
It seems like the code sandbox is broken with vue/nuxt right now, https://github.com/codesandbox/codesandbox-client/issues/8451
GitHub
Vue purported vnode error in CodeSandbox Β· Issue #8451 Β· codesandbo...
πŸ› bug report Preflight Checklist [x ] I have read the Contributing Guidelines for this project. [x ] I agree to follow the Code of Conduct that this project adheres to. [x ] I have searched the iss...
manniL
manniLβ€’7mo ago
try stackblitz instead πŸ™‚
Shuvi ☁ πŸͺ½
Shuvi ☁ πŸͺ½OPβ€’7mo ago
ShuviSchwarze
StackBlitz
Nuxt - Starter (forked) - StackBlitz
Create a new Nuxt project, module, layer or start from a theme with our collection of starters.
Shuvi ☁ πŸͺ½
Shuvi ☁ πŸͺ½OPβ€’7mo ago
I couldnt get console.log to work so I just have the sideeffect as a state count instead
Shuvi ☁ πŸͺ½
Shuvi ☁ πŸͺ½OPβ€’7mo ago
No description
Shuvi ☁ πŸͺ½
Shuvi ☁ πŸͺ½OPβ€’7mo ago
No description
Shuvi ☁ πŸͺ½
Shuvi ☁ πŸͺ½OPβ€’7mo ago
<script setup lang="ts">
import SomeButton from './SomeButton.vue';
import { useStore } from './useStore';

const { count, sideEffectCount, sideEffectComputedCount, computedState } =
useStore();
</script>

<template>
<div>
<div>Count = {{ count }}</div>
<div>computedState = {{ computedState }}</div>
<div>sideEffectCount = {{ sideEffectCount }}</div>
<div>sideEffectComputedCount = {{ sideEffectComputedCount }}</div>
<SomeButton />
<SomeButton />
<SomeButton />
</div>
</template>
<script setup lang="ts">
import SomeButton from './SomeButton.vue';
import { useStore } from './useStore';

const { count, sideEffectCount, sideEffectComputedCount, computedState } =
useStore();
</script>

<template>
<div>
<div>Count = {{ count }}</div>
<div>computedState = {{ computedState }}</div>
<div>sideEffectCount = {{ sideEffectCount }}</div>
<div>sideEffectComputedCount = {{ sideEffectComputedCount }}</div>
<SomeButton />
<SomeButton />
<SomeButton />
</div>
</template>
Some button has the hook mounted, so in stotal the watch effect is mounted 4 times
Shuvi ☁ πŸͺ½
Shuvi ☁ πŸͺ½OPβ€’7mo ago
@manniL / TheAlexLichter sorry for bumping this, but I ran into another behaviour in having side effects in computed, this time interacting with pinia store https://stackblitz.com/edit/nuxt-starter-izzlz4?file=README.md
ShuviSchwarze
StackBlitz
Nuxt - Starter (forked) - StackBlitz
Create a new Nuxt project, module, layer or start from a theme with our collection of starters.
Shuvi ☁ πŸͺ½
Shuvi ☁ πŸͺ½OPβ€’7mo ago
import { defineStore } from 'pinia';

export const useStore = () => {
const count = useState('count', () => 0);
const sideEffectCount = useState('sideEffect', () => 0);
const sideEffectComputedCount = useState('sideEffectComputed', () => 0);

watch(count, () => sideEffectCount.value++);
const computedState = computed(() => {
const something = count.value;
sideEffectComputedCount.value++;
return something;
});

return { count, sideEffectCount, sideEffectComputedCount, computedState };
};

export const useCounterStore = defineStore('counter', () => {
const count = ref(0);
const doubleCount = computed(() => count.value * 2);
function increment() {
count.value++;
}

return { count, doubleCount, increment };
});
import { defineStore } from 'pinia';

export const useStore = () => {
const count = useState('count', () => 0);
const sideEffectCount = useState('sideEffect', () => 0);
const sideEffectComputedCount = useState('sideEffectComputed', () => 0);

watch(count, () => sideEffectCount.value++);
const computedState = computed(() => {
const something = count.value;
sideEffectComputedCount.value++;
return something;
});

return { count, sideEffectCount, sideEffectComputedCount, computedState };
};

export const useCounterStore = defineStore('counter', () => {
const count = ref(0);
const doubleCount = computed(() => count.value * 2);
function increment() {
count.value++;
}

return { count, doubleCount, increment };
});
somehow computed is reacting to changes from count in useCounterStore Is there any explanation to why this is the behavior? It seems like computed is prone to rerunning too often. I know having side effects in computed is a bad practice, but this firing behavior would affect cases where computed function is expensive too
manniL
manniLβ€’7mo ago
FYI computeds also shouldn’t have side effects Better to watch that computed As there is no guarantee how often it’ll be executed/recalculated
Shuvi ☁ πŸͺ½
Shuvi ☁ πŸͺ½OPβ€’7mo ago
but watch in this case is also re-executing multiple times as seen with the initial minimal reproduction https://stackblitz.com/edit/nuxt-starter-d92zxs?file=useStore.ts,app.vue I dont think either watch or computed are reliable inside a custom hook if you have any side effect at all
StackBlitz
Nuxt - Starter (forked) - StackBlitz
Create a new Nuxt project, module, layer or start from a theme with our collection of starters.
No description
Shuvi ☁ πŸͺ½
Shuvi ☁ πŸͺ½OPβ€’7mo ago
Using watch in pinia with the same pattern seems to produce the appropriate behaviour
manniL
manniLβ€’7mo ago
no no, it is correct as you create a watcher per button when initiating the composable (for each SomeButton)
Shuvi ☁ πŸͺ½
Shuvi ☁ πŸͺ½OPβ€’7mo ago
Yes, but isnt setting state with this pattern not ideal? I dont think it is appropriate for the use case Do you suggest using any other pattern or just stick with pinia?
manniL
manniLβ€’7mo ago
Unfortunately "it depends" - basically on what you want to do pinia is totally fine πŸ™‚
Shuvi ☁ πŸͺ½
Shuvi ☁ πŸͺ½OPβ€’7mo ago
I guess for these kind of watch Ill just use a pinia store then, though I do think it is a bit of a pitfall that computed state is running so often. Thank you for your timeπŸ™‚
manniL
manniLβ€’7mo ago
no problem at all!
Want results from more Discord servers?
Add your server