Skip to content

Instantly share code, notes, and snippets.

@freehuntx
Last active February 27, 2023 01:42
Show Gist options
  • Save freehuntx/a78ae3162b350af67b24d4a68ffc1217 to your computer and use it in GitHub Desktop.
Save freehuntx/a78ae3162b350af67b24d4a68ffc1217 to your computer and use it in GitHub Desktop.
reactive-react
import hash from 'hash-it';
import { useEffect, useState } from 'react';
const MIN_DELAY = 50;
const MAX_DELAY = 1000;
const STEP_DELAY = 50;
export function useReactive(target) {
const [val, setVal] = useState(target);
const [_toggle, setToggle] = useState(false);
useEffect(() => {
let delay = MIN_DELAY;
let lastHash = 0;
let timeout;
const wait = () => {
const newHash = hash(val);
const changed = lastHash !== newHash;
lastHash = newHash;
if (changed) {
setToggle((b) => !b);
delay -= STEP_DELAY;
if (delay < MIN_DELAY) delay = MIN_DELAY;
} else {
delay += STEP_DELAY;
if (delay > MAX_DELAY) delay = MAX_DELAY;
}
timeout = setTimeout(wait, delay);
};
wait();
return () => {
clearTimeout(timeout);
};
}, [val]);
return [val, setVal];
}
import { useEffect, useState } from 'react';
function getAllPropertyDescriptors(obj, descriptors = Object.create(null)) {
if (!obj) return descriptors;
const parentProto = Object.getPrototypeOf(obj);
if (
parentProto &&
parentProto !== Object.prototype &&
parentProto !== Function.prototype
) {
Object.assign(descriptors, parentProto);
getAllPropertyDescriptors(parentProto, descriptors);
}
const ownProto = Object.getOwnPropertyDescriptors(obj);
if (ownProto !== Object.prototype && ownProto !== Function.prototype) {
Object.assign(descriptors, ownProto);
}
return descriptors;
}
class Reactive {
_watcher = [];
_unwatcher = [];
_target = null;
static from(target) {
if (!target) return null;
if (target instanceof Reactive) return target;
if (target?.$reactive) return target.$reactive;
const type = typeof target;
if (type !== 'object' && type !== 'function') return null;
return new Reactive(target);
}
constructor(target) {
if (target.$reactive) throw new Error('Target already reactive');
if (target instanceof Reactive)
throw new Error('Target is an "Reactive" instance');
this._target = target;
this._init();
}
_init() {
if (!this._target) return;
if (this._target === null) return;
if (typeof this._target !== 'object' && typeof this._target !== 'function')
return;
if (Array.isArray(this._target)) {
for (const method of ['pop', 'reverse', 'shift', 'splice', 'sort']) {
this._target[method] = (...args) => {
const result = Array.prototype[method].apply(this._target, args);
this._emitChange();
return result;
};
}
for (const method of ['push', 'unshift']) {
this._target[method] = (...args) => {
const result = Array.prototype[method].apply(this._target, args);
args.forEach((arg) => {
Reactive.from(arg)?.watch(() => {
this._emitChange();
});
// TODO: Unwatch when popped/removed
});
this._emitChange();
return result;
};
}
}
if (this._target instanceof Map) {
for (const method of ['set', 'delete', 'clear']) {
this._target[method] = (...args) => {
const result = Map.prototype[method].apply(this._target, args);
this._emitChange();
return result;
};
}
}
if (this._target instanceof Set) {
for (const method of ['add', 'delete', 'clear']) {
this._target[method] = (...args) => {
const result = Set.prototype[method].apply(this._target, args);
this._emitChange();
return result;
};
}
}
const descriptors = getAllPropertyDescriptors(this._target);
for (const [propName, descriptor] of Object.entries(descriptors)) {
if (propName === 'constructor') continue;
if (descriptor.get) {
Reactive.from(descriptor.get.apply(this._target))?.watch(() => {
this._emitChange();
});
}
if (descriptor.set && descriptor.configurable) {
Object.defineProperty(this._target, propName, {
configurable: true,
set: (val) => {
const result = descriptor.set.apply(this._target, [val]);
this._emitChange();
return result;
},
});
}
if (descriptor.value !== undefined) {
if (
typeof descriptor.value === 'object' ||
typeof descriptor.value === 'function'
) {
Reactive.from(descriptor.value)?.watch(() => {
this._emitChange();
});
} else if (!descriptor.set && descriptor.configurable) {
let val = this._target[propName];
Object.defineProperty(this._target, propName, {
configurable: true,
get: () => val,
set: (newVal) => {
val = newVal;
this._emitChange();
},
});
}
}
if (this._target[propName] !== undefined) {
Reactive.from(this._target[propName])?.watch(() => {
this._emitChange();
});
}
}
}
_emitChange() {
this._watcher.forEach((cb) => cb());
}
watch(cb) {
this.unwatch(cb);
this._watcher.push(cb);
return () => {
this.unwatch(cb);
};
}
unwatch(cb) {
this._watcher.splice(this._watcher.indexOf(cb), 1);
}
}
export function useReactive(target) {
const [val, setVal] = useState(target);
const [_toggle, setToggle] = useState(false);
useEffect(() => {
return Reactive.from(val)?.watch(() => {
setToggle((b) => !b);
});
}, [val]);
return [val, setVal];
}
import { useEffect, useState } from 'react';
function getPrototypeChain(obj, chain = []) {
if (obj === null || obj === undefined) return chain;
let prototype = Object.getPrototypeOf(obj);
if (prototype) {
chain.push(prototype);
getPrototypeChain(prototype, chain);
}
return chain;
}
function getPrototypeDescriptorChain(
obj,
chain = []
): (TypedPropertyDescriptor<any> | PropertyDescriptor)[] {
if (obj === null || obj === undefined) return chain;
const descriptor = Object.getOwnPropertyDescriptors(obj);
if (descriptor) {
chain.push(descriptor);
}
getPrototypeChain(obj).forEach((prototype) => {
if (prototype === Object.prototype || prototype === Function.prototype)
return;
const descriptor = Object.getOwnPropertyDescriptors(prototype);
if (descriptor) {
chain.push(descriptor);
}
});
return chain;
}
function getAllPropertyDescriptors(
obj
): TypedPropertyDescriptor<any> | PropertyDescriptor {
const allDescriptors = Object.create(null);
if (!obj) return allDescriptors;
getPrototypeDescriptorChain(obj)
.reverse()
.forEach((descriptors) => {
if (descriptors) Object.assign(allDescriptors, descriptors);
});
delete allDescriptors['prototype'];
Object.getOwnPropertyNames(Object.prototype).forEach((propName) => {
delete allDescriptors[propName];
});
if (typeof obj === 'function') {
Object.getOwnPropertyNames(Function.prototype).forEach((propName) => {
delete allDescriptors[propName];
});
}
return allDescriptors;
}
class Reactive {
_watcher = [];
_unwatcher = [];
_target = null;
static from(target) {
if (!target) return null;
if (target instanceof Reactive) return target;
if (target?.$reactive) return target.$reactive;
const type = typeof target;
if (type !== 'object' && type !== 'function') return null;
return new Reactive(target);
}
constructor(target) {
if (target.$reactive) throw new Error('Target already reactive');
if (target instanceof Reactive)
throw new Error('Target is an "Reactive" instance');
this._target = target;
this._init();
}
_init() {
if (!this._target) return;
if (this._target === null) return;
if (typeof this._target !== 'object' && typeof this._target !== 'function')
return;
if (Array.isArray(this._target)) {
for (const method of ['pop', 'reverse', 'shift', 'splice', 'sort']) {
this._target[method] = (...args) => {
const result = Array.prototype[method].apply(this._target, args);
this._emitChange();
return result;
};
}
for (const method of ['push', 'unshift']) {
this._target[method] = (...args) => {
const result = Array.prototype[method].apply(this._target, args);
args.forEach((arg) => {
Reactive.from(arg)?.watch(() => {
this._emitChange();
});
// TODO: Unwatch when popped/removed
});
this._emitChange();
return result;
};
}
}
if (this._target instanceof Map) {
for (const method of ['set', 'delete', 'clear']) {
this._target[method] = (...args) => {
const result = Map.prototype[method].apply(this._target, args);
this._emitChange();
return result;
};
}
}
if (this._target instanceof Set) {
for (const method of ['add', 'delete', 'clear']) {
this._target[method] = (...args) => {
const result = Set.prototype[method].apply(this._target, args);
this._emitChange();
return result;
};
}
}
const descriptors = getAllPropertyDescriptors(this._target);
for (const [propName, descriptor] of Object.entries(descriptors)) {
if (descriptor.get) {
Reactive.from(descriptor.get.apply(this._target))?.watch(() => {
this._emitChange();
});
}
if (descriptor.set && descriptor.configurable) {
Object.defineProperty(this._target, propName, {
configurable: true,
set: (val) => {
const result = descriptor.set.apply(this._target, [val]);
this._emitChange();
return result;
},
});
}
if (descriptor.value !== undefined) {
if (
typeof descriptor.value === 'object' ||
typeof descriptor.value === 'function'
) {
Reactive.from(descriptor.value)?.watch(() => {
this._emitChange();
});
} else if (!descriptor.set && descriptor.configurable) {
let val = this._target[propName];
Object.defineProperty(this._target, propName, {
configurable: true,
get: () => val,
set: (newVal) => {
val = newVal;
this._emitChange();
},
});
}
}
if (this._target[propName] !== undefined) {
Reactive.from(this._target[propName])?.watch(() => {
this._emitChange();
});
}
}
}
_emitChange() {
this._watcher.forEach((cb) => cb());
}
watch(cb) {
this.unwatch(cb);
this._watcher.push(cb);
return () => {
this.unwatch(cb);
};
}
unwatch(cb) {
this._watcher.splice(this._watcher.indexOf(cb), 1);
}
}
export function useReactive(target) {
const [val, setVal] = useState(target);
const [_toggle, setToggle] = useState(false);
useEffect(() => {
return Reactive.from(val)?.watch(() => {
setToggle((b) => !b);
});
}, [val]);
return [val, setVal];
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment