Last active
September 13, 2022 09:55
-
-
Save CodyJasonBennett/3fb8c092db76da4761e53908752340b1 to your computer and use it in GitHub Desktop.
signals-react fix
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { | |
useRef, | |
useMemo, | |
// @ts-ignore-next-line | |
// eslint-disable-next-line @typescript-eslint/no-unused-vars | |
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED as internals, | |
} from 'react' | |
import React from 'react' | |
import { signal, computed, batch, effect, Signal } from '@preact/signals-core' | |
export { signal, computed, batch, effect, Signal } | |
/** | |
* Install a middleware into React.createElement to replace any Signals in props with their value. | |
* @todo this likely needs to be duplicated for jsx()... | |
*/ | |
const createElement = React.createElement | |
// @ts-ignore-next-line | |
React.createElement = function (type, props) { | |
if (typeof type === 'string' && props) { | |
for (let i in props) { | |
let v = props[i] | |
if (i !== 'children' && v instanceof Signal) { | |
// createPropUpdater(props, i, v); | |
props[i] = v.value | |
} | |
} | |
} | |
// @ts-ignore-next-line | |
return createElement.apply(this, arguments) | |
} | |
let finishUpdate | |
function setCurrentUpdater(updater) { | |
// end tracking for the current update: | |
if (finishUpdate) finishUpdate(true, true) | |
// start tracking the new update: | |
finishUpdate = updater && updater._() | |
} | |
function createUpdater(updater) { | |
const s = signal(undefined) | |
s._c = true | |
s._u = updater | |
return s | |
} | |
/** | |
* A wrapper component that renders a Signal's value directly as a Text node. | |
*/ | |
function Text({ data }) { | |
return data.value | |
} | |
// Decorate Signals so React renders them as <Text> components. | |
//@ts-ignore-next-line | |
const $$typeof = createElement('a').$$typeof | |
Object.defineProperties(Signal.prototype, { | |
$$typeof: { value: $$typeof }, | |
type: { value: Text }, | |
props: { | |
get() { | |
return { data: this } | |
}, | |
}, | |
ref: { value: null }, | |
}) | |
// Track the current owner Fiber (roughly equiv to current component handle) | |
let lastComponent | |
let currentOwner | |
Object.defineProperty(internals.ReactCurrentOwner, 'current', { | |
get() { | |
return currentOwner | |
}, | |
set(value) { | |
currentOwner = value | |
if (currentOwner) lastComponent = currentOwner | |
}, | |
}) | |
const updaterForComponent = new WeakMap() | |
// Track the current dispatcher (roughly equiv to current component hooks -- invalid, mount, update, re-render) | |
let lock = false | |
const UPDATE = () => ({}) | |
let currentDispatcher | |
Object.defineProperty(internals.ReactCurrentDispatcher, 'current', { | |
get() { | |
return currentDispatcher | |
}, | |
set(api) { | |
currentDispatcher = api | |
if (lock) return | |
if (lastComponent && api && !isInvalidHookAccessor(api)) { | |
// prevent re-injecting useReducer when the Dispatcher | |
// context changes to run the reducer callback: | |
lock = true | |
const rerender = api.useReducer(UPDATE, {})[1] | |
lock = false | |
let updater = updaterForComponent.get(lastComponent) | |
if (!updater) { | |
updater = createUpdater(rerender) | |
updaterForComponent.set(lastComponent, updater) | |
} else { | |
updater._u = rerender | |
} | |
setCurrentUpdater(updater) | |
} else { | |
setCurrentUpdater() | |
} | |
}, | |
}) | |
// We inject a useReducer into every function component via CurrentDispatcher. | |
// This prevents injecting into anything other than a function component render. | |
const invalidHookAccessors = new Map() | |
function isInvalidHookAccessor(api) { | |
const cached = invalidHookAccessors.get(api) | |
if (cached !== undefined) return cached | |
// we only want the real implementation, not the warning ones | |
const invalid = api.useCallback.length < 2 || /warnInvalidHookAccess/.test(api.useCallback) | |
invalidHookAccessors.set(api, invalid) | |
return invalid | |
} | |
export function useSignal(value) { | |
return useMemo(() => signal(value), []) | |
} | |
export function useComputed(compute) { | |
const $compute = useRef(compute) | |
$compute.current = compute | |
return useMemo(() => computed(() => $compute.current()), []) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment