react-external-subject
Version:
Wrap mutable sources into React-safe mutable source
174 lines (170 loc) • 4.4 kB
JavaScript
// src/create-external-subject.ts
function defaultShouldUpdate(a, b) {
return !Object.is(a, b);
}
function createExternalSubject(options) {
var _a;
const shouldUpdate = (_a = options.shouldUpdate) != null ? _a : defaultShouldUpdate;
let cache;
const listeners = new Set();
let request;
const getCachedValue = () => {
if (!cache) {
cache = {
value: options.read()
};
}
return cache.value;
};
let synchronize;
const setSynchronizer = (sync) => {
if (!synchronize) {
synchronize = sync;
}
};
const requestUpdate = () => {
if (shouldUpdate(getCachedValue(), options.read())) {
if (request) {
request.alive = false;
}
request = {
promise: Promise.resolve().then(() => new Promise((resolve) => {
if (request == null ? void 0 : request.alive) {
request = void 0;
const onResolve = () => {
if (cache) {
cache.value = options.read();
}
listeners.forEach((listener) => {
listener();
});
resolve();
};
if (synchronize) {
synchronize(onResolve);
} else {
onResolve();
}
}
})),
alive: true
};
}
};
let unsubscribe;
const subscribe = (handler) => {
listeners.add(handler);
if (options.lazySubscribe && options.subscribe && listeners.size === 1) {
const unsub = options.subscribe(requestUpdate);
if (unsub) {
unsubscribe = unsub;
}
}
return () => {
listeners.delete(handler);
if (options.lazySubscribe && unsubscribe) {
unsubscribe();
}
};
};
if (!options.lazySubscribe && options.subscribe) {
unsubscribe = options.subscribe(requestUpdate);
}
const destroy = () => {
if (request) {
request.alive = false;
}
listeners.clear();
if (unsubscribe) {
unsubscribe();
}
};
return {
subscribe,
shouldUpdate,
requestUpdate,
getRequest: () => request,
getCachedValue,
getCurrentValue: options.read,
setSynchronizer,
destroy
};
}
// src/use-external-subject.ts
import {
useMemoCondition,
useSubscription
} from "@lyonph/react-hooks";
import {useDebugValue} from "react";
// src/ExternalSubjectSynchronizer.tsx
import React, {
createContext,
Suspense,
useContext,
useEffect,
useState
} from "react";
import {useConstantCallback} from "@lyonph/react-hooks";
var ExternalSubjectSynchronizerContext = createContext(void 0);
function useExternalSubjectSynchronize() {
const context = useContext(ExternalSubjectSynchronizerContext);
if (context) {
return context;
}
throw new Error("Found no synchronizer.");
}
function ExternalSubjectSynchronizer({children}) {
const [state, setState] = useState([]);
const synchronize = useConstantCallback((cb) => {
setState((current) => [...current, cb]);
});
useEffect(() => {
if (state.length) {
setState([]);
state.forEach((update) => {
update();
});
}
}, [state]);
return /* @__PURE__ */ React.createElement(ExternalSubjectSynchronizerContext.Provider, {
value: synchronize
}, /* @__PURE__ */ React.createElement(Suspense, {
fallback: null
}, children));
}
// src/use-external-subject.ts
function useExternalSubjectInternal(subject, suspense = false) {
subject.setSynchronizer(useExternalSubjectSynchronize());
const subscription = useMemoCondition(() => ({
read: () => subject.getCachedValue(),
subscribe: (handler) => subject.subscribe(handler),
shouldUpdate: (a, b) => subject.shouldUpdate(a, b)
}), subject);
const state = useSubscription(subscription);
const ongoing = subject.getRequest();
if (ongoing) {
if (suspense) {
throw ongoing.promise;
}
} else {
subject.requestUpdate();
const current = subject.getRequest();
if (current) {
if (suspense) {
throw current.promise;
}
}
}
return state;
}
function useExternalSubject(subject, suspense = false) {
const value = useExternalSubjectInternal(subject, suspense);
useDebugValue(value);
return value;
}
export {
ExternalSubjectSynchronizer,
createExternalSubject,
useExternalSubject
};
//# sourceMappingURL=react-external-subject.esm.js.map