Skip to content

Instantly share code, notes, and snippets.

@CodyJasonBennett
Last active September 13, 2022 09:55
Show Gist options
  • Save CodyJasonBennett/3fb8c092db76da4761e53908752340b1 to your computer and use it in GitHub Desktop.
Save CodyJasonBennett/3fb8c092db76da4761e53908752340b1 to your computer and use it in GitHub Desktop.
signals-react fix
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