Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Nuxt/useLocalStorage]: Using useLocalStoage as state and hydrate function, TS2322: Type 'RemovableRef<{}>' is not assignable to type "xxx" or lost reactivity. #2086

Closed
AnzhiZhang opened this issue Mar 20, 2023 · 1 comment

Comments

@AnzhiZhang
Copy link

Reproduction

https://stackblitz.com/edit/github-1bnqyz?file=stores/counter.ts

Steps to reproduce the bug

  1. There is a state named n, its type is number | undefined.
  2. Use hydrate function to set value useLocalStorage('storage', 0) to it (IMPORTANT: not skipHydrate)
  3. Then will be a typescript erro "Type 'RemovableRef' is not assignable to type 'string| undefined'".

Expected behavior

Recognize the correct type of it.

Actual behavior

Type error.

Additional information

skipHydrate

This issue will only work on hydrate function instead of skipHydrate, I created another store named example.ts. It works fine as in the document https://pinia.vuejs.org/cookbook/composables.html#ssr.

In other words, only the options API will have the issue, the composition API is fine.

Did I write a wrong type of the state?

No I'm not. Remove the type and use same init value in the state option, the issue still exists. (There is a commented line in the reproduction)

Try to useLocalStorage().value

The useLocalStorage returns a Ref, so assign it's value property to the state could work (also a commented line).

But the reactivity will be lost. Launch the reproduction and open a new tab of it, use F12 to view and edit the Local Storages, and try to change the Local Storages or the input element, you will see the reactivity by useLocalStorage. This reactivity will not be lost when using useLocalStorage().value to avoid this typescript error.

An attempt to fin the reason

Since only hydrate has this problem, I noticed hydrate's first argument is UnwrapRef<S>, and this S is StateTree.

export interface DefineStoreOptions<
Id extends string,
S extends StateTree,
G /* extends GettersTree<S> */,
A /* extends Record<string, StoreAction> */
> extends DefineStoreOptionsBase<S, Store<Id, S, G, A>> {
/**
* Unique string key to identify the store across the application.
*/
id: Id
/**
* Function to create a fresh state. **Must be an arrow function** to ensure
* correct typings!
*/
state?: () => S
/**
* Optional object of getters.
*/
getters?: G &
ThisType<UnwrapRef<S> & _StoreWithGetters<G> & PiniaCustomProperties> &
_GettersTree<S>
/**
* Optional object of actions.
*/
actions?: A &
ThisType<
A &
UnwrapRef<S> &
_StoreWithState<Id, S, G, A> &
_StoreWithGetters<G> &
PiniaCustomProperties
>
/**
* Allows hydrating the store during SSR when complex state (like client side only refs) are used in the store
* definition and copying the value from `pinia.state` isn't enough.
*
* @example
* If in your `state`, you use any `customRef`s, any `computed`s, or any `ref`s that have a different value on
* Server and Client, you need to manually hydrate them. e.g., a custom ref that is stored in the local
* storage:
*
* ```ts
* const useStore = defineStore('main', {
* state: () => ({
* n: useLocalStorage('key', 0)
* }),
* hydrate(storeState, initialState) {
* // @ts-expect-error: https://github.com/microsoft/TypeScript/issues/43826
* storeState.n = useLocalStorage('key', 0)
* }
* })
* ```
*
* @param storeState - the current state in the store
* @param initialState - initialState
*/
hydrate?(storeState: UnwrapRef<S>, initialState: UnwrapRef<S>): void
}

Copy link
Member

posva commented Apr 3, 2023

This is what you need to make it work:

import { useLocalStorage } from '@vueuse/core';

export const useCounter = defineStore('counter', {
  state: () => ({
    n: useLocalStorage('storage', "test"),
  }),
  hydrate(state) {
    // @ts-expect-error: ref
    state.n = useLocalStorage('storage', 'test');
  },
});

The type difference is necessary because of a limitation of TypeScript and there is already an open issue on Vue core

@posva posva closed this as not planned Won't fix, can't repro, duplicate, stale Apr 3, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants