zustand
Version:
🐻 Bear necessities for state management in React
464 lines (391 loc) • 16.1 kB
JavaScript
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define(['exports'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.zustand = {}));
})(this, (function (exports) { 'use strict';
function _extends() {
_extends = Object.assign || function (target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
for (var key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
target[key] = source[key];
}
}
}
return target;
};
return _extends.apply(this, arguments);
}
var redux = function redux(reducer, initial) {
return function (set, get, api) {
api.dispatch = function (action) {
set(function (state) {
return reducer(state, action);
}, false, action);
return action;
};
api.dispatchFromDevtools = true;
return _extends({
dispatch: function dispatch() {
return api.dispatch.apply(api, arguments);
}
}, initial);
};
};
function devtools(fn, options) {
return function (set, get, api) {
var didWarnAboutNameDeprecation = false;
if (typeof options === 'string' && !didWarnAboutNameDeprecation) {
console.warn('[zustand devtools middleware]: passing `name` as directly will be not allowed in next major' + 'pass the `name` in an object `{ name: ... }` instead');
didWarnAboutNameDeprecation = true;
}
var devtoolsOptions = options === undefined ? {
name: undefined,
anonymousActionType: undefined
} : typeof options === 'string' ? {
name: options
} : options;
var extensionConnector;
try {
extensionConnector = window.__REDUX_DEVTOOLS_EXTENSION__ || window.top.__REDUX_DEVTOOLS_EXTENSION__;
} catch (_unused) {}
if (!extensionConnector) {
if (process.env.NODE_ENV === 'development' && typeof window !== 'undefined') {
console.warn('[zustand devtools middleware] Please install/enable Redux devtools extension');
}
return fn(set, get, api);
}
var extension = Object.create(extensionConnector.connect(devtoolsOptions));
var didWarnAboutDevtools = false;
Object.defineProperty(api, 'devtools', {
get: function get() {
if (!didWarnAboutDevtools) {
console.warn('[zustand devtools middleware] `devtools` property on the store is deprecated ' + 'it will be removed in the next major.\n' + "You shouldn't interact with the extension directly. But in case you still want to " + 'you can patch `window.__REDUX_DEVTOOLS_EXTENSION__` directly');
didWarnAboutDevtools = true;
}
return extension;
},
set: function set(value) {
if (!didWarnAboutDevtools) {
console.warn('[zustand devtools middleware] `api.devtools` is deprecated, ' + 'it will be removed in the next major.\n' + "You shouldn't interact with the extension directly. But in case you still want to " + 'you can patch `window.__REDUX_DEVTOOLS_EXTENSION__` directly');
didWarnAboutDevtools = true;
}
extension = value;
}
});
var didWarnAboutPrefix = false;
Object.defineProperty(extension, 'prefix', {
get: function get() {
if (!didWarnAboutPrefix) {
console.warn('[zustand devtools middleware] along with `api.devtools`, `api.devtools.prefix` is deprecated.\n' + 'We no longer prefix the actions/names' + devtoolsOptions.name === undefined ? ', pass the `name` option to create a separate instance of devtools for each store.' : ', because the `name` option already creates a separate instance of devtools for each store.');
didWarnAboutPrefix = true;
}
return '';
},
set: function set() {
if (!didWarnAboutPrefix) {
console.warn('[zustand devtools middleware] along with `api.devtools`, `api.devtools.prefix` is deprecated.\n' + 'We no longer prefix the actions/names' + devtoolsOptions.name === undefined ? ', pass the `name` option to create a separate instance of devtools for each store.' : ', because the `name` option already creates a separate instance of devtools for each store.');
didWarnAboutPrefix = true;
}
}
});
var isRecording = true;
api.setState = function (state, replace, nameOrAction) {
set(state, replace);
if (!isRecording) return;
extension.send(nameOrAction === undefined ? {
type: devtoolsOptions.anonymousActionType || 'anonymous'
} : typeof nameOrAction === 'string' ? {
type: nameOrAction
} : nameOrAction, get());
};
var setStateFromDevtools = function setStateFromDevtools() {
var originalIsRecording = isRecording;
isRecording = false;
set.apply(void 0, arguments);
isRecording = originalIsRecording;
};
var initialState = fn(api.setState, get, api);
extension.init(initialState);
if (api.dispatchFromDevtools && typeof api.dispatch === 'function') {
var didWarnAboutReservedActionType = false;
var originalDispatch = api.dispatch;
api.dispatch = function () {
for (var _len = arguments.length, a = new Array(_len), _key = 0; _key < _len; _key++) {
a[_key] = arguments[_key];
}
if (a[0].type === '__setState' && !didWarnAboutReservedActionType) {
console.warn('[zustand devtools middleware] "__setState" action type is reserved ' + 'to set state from the devtools. Avoid using it.');
didWarnAboutReservedActionType = true;
}
originalDispatch.apply(void 0, a);
};
}
extension.subscribe(function (message) {
switch (message.type) {
case 'ACTION':
if (typeof message.payload !== 'string') {
console.error('[zustand devtools middleware] Unsupported action format');
return;
}
return parseJsonThen(message.payload, function (action) {
if (action.type === '__setState') {
setStateFromDevtools(action.state);
return;
}
if (!api.dispatchFromDevtools) return;
if (typeof api.dispatch !== 'function') return;
api.dispatch(action);
});
case 'DISPATCH':
switch (message.payload.type) {
case 'RESET':
setStateFromDevtools(initialState);
return extension.init(api.getState());
case 'COMMIT':
return extension.init(api.getState());
case 'ROLLBACK':
return parseJsonThen(message.state, function (state) {
setStateFromDevtools(state);
extension.init(api.getState());
});
case 'JUMP_TO_STATE':
case 'JUMP_TO_ACTION':
return parseJsonThen(message.state, function (state) {
setStateFromDevtools(state);
});
case 'IMPORT_STATE':
{
var _nextLiftedState$comp;
var nextLiftedState = message.payload.nextLiftedState;
var lastComputedState = (_nextLiftedState$comp = nextLiftedState.computedStates.slice(-1)[0]) == null ? void 0 : _nextLiftedState$comp.state;
if (!lastComputedState) return;
setStateFromDevtools(lastComputedState);
extension.send(null, nextLiftedState);
return;
}
case 'PAUSE_RECORDING':
return isRecording = !isRecording;
}
return;
}
});
return initialState;
};
}
var parseJsonThen = function parseJsonThen(stringified, f) {
var parsed;
try {
parsed = JSON.parse(stringified);
} catch (e) {
console.error('[zustand devtools middleware] Could not parse the received json', e);
}
if (parsed !== undefined) f(parsed);
};
var subscribeWithSelector = function subscribeWithSelector(fn) {
return function (set, get, api) {
var origSubscribe = api.subscribe;
api.subscribe = function (selector, optListener, options) {
var listener = selector;
if (optListener) {
var equalityFn = (options == null ? void 0 : options.equalityFn) || Object.is;
var currentSlice = selector(api.getState());
listener = function listener(state) {
var nextSlice = selector(state);
if (!equalityFn(currentSlice, nextSlice)) {
var previousSlice = currentSlice;
optListener(currentSlice = nextSlice, previousSlice);
}
};
if (options != null && options.fireImmediately) {
optListener(currentSlice, currentSlice);
}
}
return origSubscribe(listener);
};
var initialState = fn(set, get, api);
return initialState;
};
};
var combine = function combine(initialState, create) {
return function (set, get, api) {
return Object.assign({}, initialState, create(set, get, api));
};
};
var toThenable = function toThenable(fn) {
return function (input) {
try {
var result = fn(input);
if (result instanceof Promise) {
return result;
}
return {
then: function then(onFulfilled) {
return toThenable(onFulfilled)(result);
},
catch: function _catch(_onRejected) {
return this;
}
};
} catch (e) {
return {
then: function then(_onFulfilled) {
return this;
},
catch: function _catch(onRejected) {
return toThenable(onRejected)(e);
}
};
}
};
};
var persist = function persist(config, baseOptions) {
return function (set, get, api) {
var options = _extends({
getStorage: function getStorage() {
return localStorage;
},
serialize: JSON.stringify,
deserialize: JSON.parse,
partialize: function partialize(state) {
return state;
},
version: 0,
merge: function merge(persistedState, currentState) {
return _extends({}, currentState, persistedState);
}
}, baseOptions);
if (options.blacklist || options.whitelist) {
console.warn("The " + (options.blacklist ? 'blacklist' : 'whitelist') + " option is deprecated and will be removed in the next version. Please use the 'partialize' option instead.");
}
var _hasHydrated = false;
var hydrationListeners = new Set();
var finishHydrationListeners = new Set();
var storage;
try {
storage = options.getStorage();
} catch (e) {}
if (!storage) {
return config(function () {
console.warn("[zustand persist middleware] Unable to update item '" + options.name + "', the given storage is currently unavailable.");
set.apply(void 0, arguments);
}, get, api);
} else if (!storage.removeItem) {
console.warn("[zustand persist middleware] The given storage for item '" + options.name + "' does not contain a 'removeItem' method, which will be required in v4.");
}
var thenableSerialize = toThenable(options.serialize);
var setItem = function setItem() {
var state = options.partialize(_extends({}, get()));
if (options.whitelist) {
Object.keys(state).forEach(function (key) {
var _options$whitelist;
!((_options$whitelist = options.whitelist) != null && _options$whitelist.includes(key)) && delete state[key];
});
}
if (options.blacklist) {
options.blacklist.forEach(function (key) {
return delete state[key];
});
}
var errorInSync;
var thenable = thenableSerialize({
state: state,
version: options.version
}).then(function (serializedValue) {
return storage.setItem(options.name, serializedValue);
}).catch(function (e) {
errorInSync = e;
});
if (errorInSync) {
throw errorInSync;
}
return thenable;
};
var savedSetState = api.setState;
api.setState = function (state, replace) {
savedSetState(state, replace);
void setItem();
};
var configResult = config(function () {
set.apply(void 0, arguments);
void setItem();
}, get, api);
var stateFromStorage;
var hydrate = function hydrate() {
if (!storage) return;
_hasHydrated = false;
hydrationListeners.forEach(function (cb) {
return cb(get());
});
var postRehydrationCallback = (options.onRehydrateStorage == null ? void 0 : options.onRehydrateStorage(get())) || undefined;
return toThenable(storage.getItem.bind(storage))(options.name).then(function (storageValue) {
if (storageValue) {
return options.deserialize(storageValue);
}
}).then(function (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(function (migratedState) {
stateFromStorage = options.merge(migratedState, configResult);
set(stateFromStorage, true);
return setItem();
}).then(function () {
postRehydrationCallback == null ? void 0 : postRehydrationCallback(stateFromStorage, undefined);
_hasHydrated = true;
finishHydrationListeners.forEach(function (cb) {
return cb(stateFromStorage);
});
}).catch(function (e) {
postRehydrationCallback == null ? void 0 : postRehydrationCallback(undefined, e);
});
};
api.persist = {
setOptions: function setOptions(newOptions) {
options = _extends({}, options, newOptions);
if (newOptions.getStorage) {
storage = newOptions.getStorage();
}
},
clearStorage: function clearStorage() {
var _storage;
(_storage = storage) == null ? void 0 : _storage.removeItem == null ? void 0 : _storage.removeItem(options.name);
},
rehydrate: function rehydrate() {
return hydrate();
},
hasHydrated: function hasHydrated() {
return _hasHydrated;
},
onHydrate: function onHydrate(cb) {
hydrationListeners.add(cb);
return function () {
hydrationListeners.delete(cb);
};
},
onFinishHydration: function onFinishHydration(cb) {
finishHydrationListeners.add(cb);
return function () {
finishHydrationListeners.delete(cb);
};
}
};
hydrate();
return stateFromStorage || configResult;
};
};
exports.combine = combine;
exports.devtools = devtools;
exports.persist = persist;
exports.redux = redux;
exports.subscribeWithSelector = subscribeWithSelector;
Object.defineProperty(exports, '__esModule', { value: true });
}));