@anew/hooks
Version:
AnewJS React Hooks
257 lines (211 loc) • 6.69 kB
JavaScript
import { useDebugValue, useReducer, useMemo, useRef, useLayoutEffect, useEffect } from 'react';
function is(x, y) {
if (x === y) {
return x !== 0 || y !== 0 || 1 / x === 1 / y;
} else {
return x !== x && y !== y;
}
}
function shallowEqual(objA, objB) {
if (is(objA, objB)) return true;
if (typeof objA !== 'object' || objA === null || typeof objB !== 'object' || objB === null) {
return false;
}
var keysA = Object.keys(objA);
var keysB = Object.keys(objB);
if (keysA.length !== keysB.length) return false;
for (var i = 0; i < keysA.length; i++) {
if (!Object.prototype.hasOwnProperty.call(objB, keysA[i]) || !is(objA[keysA[i]], objB[keysA[i]])) {
return false;
}
}
return true;
}
// Default to a dummy "batch" implementation that just runs the callback
var defaultNoopBatch = function defaultNoopBatch(callback) {
callback();
};
var batch = defaultNoopBatch; // Allow injecting another batching function later
var getBatch = function getBatch() {
return batch;
};
var nullListeners = {
notify: function notify() {}
};
function createListenerCollection() {
var batch = getBatch();
var first = null;
var last = null;
return {
clear: function clear() {
first = null;
last = null;
},
notify: function notify() {
batch(function () {
var listener = first;
while (listener) {
listener.callback();
listener = listener.next;
}
});
},
get: function get() {
var listeners = [];
var listener = first;
while (listener) {
listeners.push(listener);
listener = listener.next;
}
return listeners;
},
subscribe: function subscribe(callback) {
var isSubscribed = true;
var listener = last = {
callback: callback,
next: null,
prev: last
};
if (listener.prev) {
listener.prev.next = listener;
} else {
first = listener;
}
return function unsubscribe() {
if (!isSubscribed || first === null) return;
isSubscribed = false;
if (listener.next) {
listener.next.prev = listener.prev;
} else {
last = listener.prev;
}
if (listener.prev) {
listener.prev.next = listener.next;
} else {
first = listener.next;
}
};
}
};
}
var Subscription = /*#__PURE__*/function () {
function Subscription(store) {
var _this = this;
this.store = store;
this.unsubscribe = null;
this.listeners = nullListeners;
this.handleChangeWrapper = function () {
if (_this.onStateChange) {
_this.onStateChange();
}
};
}
var _proto = Subscription.prototype;
_proto.notifyNestedSubs = function notifyNestedSubs() {
this.listeners.notify();
};
_proto.isSubscribed = function isSubscribed() {
return Boolean(this.unsubscribe);
};
_proto.trySubscribe = function trySubscribe() {
if (!this.unsubscribe) {
this.unsubscribe = this.store.subscribe(this.handleChangeWrapper);
this.listeners = createListenerCollection();
}
};
_proto.tryUnsubscribe = function tryUnsubscribe() {
if (this.unsubscribe) {
this.unsubscribe();
this.unsubscribe = null;
if (this.listeners && this.listeners.clear) this.listeners.clear();
this.listeners = nullListeners;
}
};
return Subscription;
}();
var refEquality = function refEquality(a, b) {
return a === b;
};
var useIsomorphicLayoutEffect = typeof window !== 'undefined' && typeof window.document !== 'undefined' && typeof window.document.createElement !== 'undefined' ? useLayoutEffect : useEffect;
function useStoreStateWithStoreAndSubscription(store, selector, equalityFn) {
var _useReducer = useReducer(function (s) {
return s + 1;
}, 0),
forceRender = _useReducer[1];
var subscription = useMemo(function () {
return new Subscription(store);
}, [store]);
var latestSubscriptionCallbackError = useRef();
var latestSelector = useRef();
var latestStoreState = useRef();
var latestSelectedState = useRef();
var storeState = store.state;
var selectedState;
try {
if (selector !== latestSelector.current || storeState !== latestStoreState.current || latestSubscriptionCallbackError.current) {
selectedState = selector(storeState);
} else {
selectedState = latestSelectedState.current;
}
} catch (err) {
if (latestSubscriptionCallbackError.current) {
err.message += "\nThe error may be correlated with this previous error:\n" + latestSubscriptionCallbackError.current.stack + "\n\n";
}
throw err;
}
useIsomorphicLayoutEffect(function () {
latestSelector.current = selector;
latestStoreState.current = storeState;
latestSelectedState.current = selectedState;
latestSubscriptionCallbackError.current = undefined;
});
useIsomorphicLayoutEffect(function () {
function checkForUpdates() {
try {
var newSelectedState = latestSelector.current(store.state);
if (equalityFn(newSelectedState, latestSelectedState.current)) {
return;
}
latestSelectedState.current = newSelectedState;
} catch (err) {
// we ignore all errors here, since when the component
// is re-rendered, the selectors are called again, and
// will throw again, if neither props nor store state
// changed
latestSubscriptionCallbackError.current = err;
}
forceRender();
}
subscription.onStateChange = checkForUpdates;
subscription.trySubscribe();
checkForUpdates();
return function () {
return subscription.tryUnsubscribe();
};
}, [store, subscription]);
return selectedState;
}
/**
* Hook factory, which creates a `useStoreState` hook bound to a given store.
*
* @param {Store} [store] Store
* @returns {Function} A `useStoreState` hook bound to the specified store.
*/
function createUseStoreState(store) {
function useStoreState(selector, equalityFn) {
if (equalityFn === void 0) {
equalityFn = refEquality;
}
if (process.env.NODE_ENV !== 'production' && !selector) {
throw new Error("You must pass a selector to useStoreState");
}
var selectedState = useStoreStateWithStoreAndSubscription(store, selector, equalityFn);
useDebugValue(selectedState);
return selectedState;
}
useStoreState.withSallowEqual = function useStoreStateWithShallowEqual(selector) {
return useStoreState(selector, shallowEqual);
};
return useStoreState;
}
export { createUseStoreState, shallowEqual };