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

[v5] breaking: drop deprecated features #2235

Merged
merged 11 commits into from
Dec 14, 2023
15 changes: 0 additions & 15 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -134,20 +134,6 @@
"types": "./traditional.d.ts",
"default": "./traditional.js"
}
},
"./context": {
"import": {
"types": "./esm/context.d.mts",
"default": "./esm/context.mjs"
},
"module": {
"types": "./esm/context.d.ts",
"default": "./esm/context.js"
},
"default": {
"types": "./context.d.ts",
"default": "./context.js"
}
}
},
"sideEffects": false,
Expand All @@ -162,7 +148,6 @@
"build:vanilla:shallow": "rollup -c --config-vanilla_shallow",
"build:react:shallow": "rollup -c --config-react_shallow",
"build:traditional": "rollup -c --config-traditional",
"build:context": "rollup -c --config-context",
charkour marked this conversation as resolved.
Show resolved Hide resolved
"postbuild": "yarn patch-d-ts && yarn copy && yarn patch-esm-ts",
"prettier": "prettier \"*.{js,json,md}\" \"{examples,src,tests,docs}/**/*.{js,jsx,ts,tsx,md,mdx}\" --write",
"prettier:ci": "prettier '*.{js,json,md}' '{examples,src,tests,docs}/**/*.{js,jsx,ts,tsx,md,mdx}' --list-different",
Expand Down
100 changes: 0 additions & 100 deletions src/context.ts

This file was deleted.

219 changes: 1 addition & 218 deletions src/middleware/persist.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,34 +66,6 @@ export function createJSONStorage<S>(
export interface PersistOptions<S, PersistedState = S> {
/** Name of the storage (must be unique) */
name: string
/**
* @deprecated Use `storage` instead.
* A function returning a storage.
* The storage must fit `window.localStorage`'s api (or an async version of it).
* For example the storage could be `AsyncStorage` from React Native.
*
* @default () => localStorage
*/
getStorage?: () => StateStorage
/**
* @deprecated Use `storage` instead.
* Use a custom serializer.
* The returned string will be stored in the storage.
*
* @default JSON.stringify
*/
serialize?: (state: StorageValue<S>) => string | Promise<string>
/**
* @deprecated Use `storage` instead.
* Use a custom deserializer.
* Must return an object matching StorageValue<S>
*
* @param str The storage's current value.
* @default JSON.parse
*/
deserialize?: (
str: string,
) => StorageValue<PersistedState> | Promise<StorageValue<PersistedState>>
/**
* Use a custom persist storage.
*
Expand Down Expand Up @@ -197,180 +169,7 @@ const toThenable =
}
}

const oldImpl: PersistImpl = (config, baseOptions) => (set, get, api) => {
type S = ReturnType<typeof config>
let options = {
getStorage: () => localStorage,
serialize: JSON.stringify as (state: StorageValue<S>) => string,
deserialize: JSON.parse as (str: string) => StorageValue<S>,
partialize: (state: S) => state,
version: 0,
merge: (persistedState: unknown, currentState: S) => ({
...currentState,
...(persistedState as object),
}),
...baseOptions,
}

let hasHydrated = false
const hydrationListeners = new Set<PersistListener<S>>()
const finishHydrationListeners = new Set<PersistListener<S>>()
let storage: StateStorage | undefined

try {
storage = options.getStorage()
} catch (e) {
// prevent error if the storage is not defined (e.g. when server side rendering a page)
}

if (!storage) {
return config(
(...args) => {
console.warn(
`[zustand persist middleware] Unable to update item '${options.name}', the given storage is currently unavailable.`,
)
set(...args)
},
get,
api,
)
}

const thenableSerialize = toThenable(options.serialize)

const setItem = (): Thenable<void> => {
const state = options.partialize({ ...get() })

let errorInSync: Error | undefined
const thenable = thenableSerialize({ state, version: options.version })
.then((serializedValue) =>
(storage as StateStorage).setItem(options.name, serializedValue),
)
.catch((e) => {
errorInSync = e
})
if (errorInSync) {
throw errorInSync
}
return thenable
}

const savedSetState = api.setState

api.setState = (state, replace) => {
savedSetState(state, replace)
void setItem()
}

const configResult = config(
(...args) => {
set(...args)
void setItem()
},
get,
api,
)

// a workaround to solve the issue of not storing rehydrated state in sync storage
// the set(state) value would be later overridden with initial state by create()
// to avoid this, we merge the state from localStorage into the initial state.
let stateFromStorage: S | undefined

// rehydrate initial state with existing stored state
const hydrate = () => {
if (!storage) return

hasHydrated = false
hydrationListeners.forEach((cb) => cb(get()))

const postRehydrationCallback =
options.onRehydrateStorage?.(get()) || undefined

// bind is used to avoid `TypeError: Illegal invocation` error
return toThenable(storage.getItem.bind(storage))(options.name)
.then((storageValue) => {
if (storageValue) {
return options.deserialize(storageValue)
}
})
.then((deserializedStorageValue) => {
if (deserializedStorageValue) {
if (
typeof deserializedStorageValue.version === 'number' &&
deserializedStorageValue.version !== options.version
) {
if (options.migrate) {
return options.migrate(
deserializedStorageValue.state,
deserializedStorageValue.version,
)
}
console.error(
`State loaded from storage couldn't be migrated since no migrate function was provided`,
)
} else {
return deserializedStorageValue.state
}
}
})
.then((migratedState) => {
stateFromStorage = options.merge(
migratedState as S,
get() ?? configResult,
)

set(stateFromStorage as S, true)
return setItem()
})
.then(() => {
postRehydrationCallback?.(stateFromStorage, undefined)
hasHydrated = true
finishHydrationListeners.forEach((cb) => cb(stateFromStorage as S))
})
.catch((e: Error) => {
postRehydrationCallback?.(undefined, e)
})
}

;(api as StoreApi<S> & StorePersist<S, S>).persist = {
setOptions: (newOptions) => {
options = {
...options,
...newOptions,
}

if (newOptions.getStorage) {
storage = newOptions.getStorage()
}
},
clearStorage: () => {
storage?.removeItem(options.name)
},
getOptions: () => options,
rehydrate: () => hydrate() as Promise<void>,
hasHydrated: () => hasHydrated,
onHydrate: (cb) => {
hydrationListeners.add(cb)

return () => {
hydrationListeners.delete(cb)
}
},
onFinishHydration: (cb) => {
finishHydrationListeners.add(cb)

return () => {
finishHydrationListeners.delete(cb)
}
},
}

hydrate()

return stateFromStorage || configResult
}

const newImpl: PersistImpl = (config, baseOptions) => (set, get, api) => {
const persistImpl: PersistImpl = (config, baseOptions) => (set, get, api) => {
charkour marked this conversation as resolved.
Show resolved Hide resolved
type S = ReturnType<typeof config>
let options = {
storage: createJSONStorage<S>(() => localStorage),
Expand Down Expand Up @@ -538,22 +337,6 @@ const newImpl: PersistImpl = (config, baseOptions) => (set, get, api) => {
return stateFromStorage || configResult
}

const persistImpl: PersistImpl = (config, baseOptions) => {
if (
'getStorage' in baseOptions ||
'serialize' in baseOptions ||
'deserialize' in baseOptions
) {
if (import.meta.env?.MODE !== 'production') {
console.warn(
'[DEPRECATED] `getStorage`, `serialize` and `deserialize` options are deprecated. Use `storage` option instead.',
)
}
return oldImpl(config, baseOptions)
}
return newImpl(config, baseOptions)
}

type Persist = <
T,
Mps extends [StoreMutatorIdentifier, unknown][] = [],
Expand Down
Loading