Skip to content

Commit

Permalink
[v5] breaking: drop deprecated features (#2235)
Browse files Browse the repository at this point in the history
* fix: remove deprecated v4 features

* chore(build): remove context

* docs(typescript): remove deprecated equals api

* docs(persist): remove old persist api

* chore: run yarn prettier on typescript docs

* Discard changes to docs/guides/typescript.md

* Discard changes to docs/integrations/persisting-store-data.md

* Discard changes to tests/shallow.test.tsx

* Discard changes to tests/vanilla/subscribe.test.tsx
  • Loading branch information
charkour committed Dec 14, 2023
1 parent 5c99468 commit 6f99da1
Show file tree
Hide file tree
Showing 9 changed files with 4 additions and 664 deletions.
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",
"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) => {
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

0 comments on commit 6f99da1

Please sign in to comment.