Skip to content

Instantly share code, notes, and snippets.

@seivan
Last active April 3, 2020 20:58
Show Gist options
  • Save seivan/329dc16e2febf6ef9eeec4c985fffcab to your computer and use it in GitHub Desktop.
Save seivan/329dc16e2febf6ef9eeec4c985fffcab to your computer and use it in GitHub Desktop.
Homemade observable
import React, { Component } from "react";
import ReactDOM from "react-dom";
import { store, observer } from "./konteks";
import "./index.css";
const state = store({
text: "",
number: 0,
increment: () => state.number++,
decrement: () => state.number--,
reset: () => (state.number = 0)
});
//<pre>{JSON.stringify(state, null, 2)}</pre>
const App = () => {
return observer(() => {
return (
<>
<button onClick={() => state.reset()}>Reset</button>
<button onClick={() => state.decrement()}>
{" "}
current: {state.number} - 1{" "}
</button>
<button onClick={() => state.increment()}>
{" "}
current: {state.number} + 1{" "}
</button>
<input
placeholder="type something"
onChange={e => (state.text = e.target.value)}
value={state.text}
/>
</>
);
});
};
ReactDOM.render(<App />, document.getElementById("root"));
import { useState, useEffect, useCallback, useRef } from "react";
if (!Object.prototype.forEach) {
Object.defineProperty(Object.prototype, "forEach", {
value: function(callback, thisArg) {
if (this == null) {
throw new TypeError("Not an object");
}
thisArg = thisArg || window;
for (var key in this) {
if (this.hasOwnProperty(key)) {
callback.call(thisArg, this[key], key, this);
}
}
}
});
}
const objectToComponents = {};
const componentToObjects = {};
let currentlyRenderingComponent;
const handler = {
get: function(target, key) {
if (typeof currentlyRenderingComponent === "undefined") {
return target[key];
}
let name = currentlyRenderingComponent.name;
let components = objectToComponents[target._id];
let objects = componentToObjects[name];
if (components === undefined) {
objectToComponents[target._id] = { [name]: currentlyRenderingComponent };
} else if (components[name] === undefined) {
components[name] = currentlyRenderingComponent;
}
if (objects === undefined) {
componentToObjects[name] = { [target._id]: true };
} else if (objects[target._id] !== true) {
objects[target._id] = true;
}
currentlyRenderingComponent = undefined;
return target[key];
},
set: function(target, key, value) {
target[key] = value;
const components = objectToComponents[target._id];
if (components != null) {
components.forEach(component => {
component.invalidate();
});
}
return true;
}
};
const getNextId = () => {
return `${Math.floor(Math.random() * 10e9)}`;
};
export function store(object) {
const obj = { ...object, ...{ _id: getNextId } };
return new Proxy(obj, handler);
}
var x = 0;
export const observer = fn => {
const name = `observer(${getNextId()})`;
const reaction = useRef(null);
//const forceUpdate = useForceUpdate();
const [, setTick] = useState(0);
const update = useCallback(() => {
setTick(tick => tick + 1);
}, []);
useEffect(() => {
return () => {
console.log("UNMOUNTED");
const objects = componentToObjects[name];
if (objects !== undefined) {
componentToObjects[name] = undefined;
objects.forEach(component => {
objectToComponents[component._id] = undefined;
});
}
};
}, []);
if (!reaction.current) {
reaction.current = {
name: name,
invalidate: () => {
console.log("invalidate");
update();
},
render: () => {
return fn();
}
};
}
currentlyRenderingComponent = reaction.current;
return reaction.current.render();
};
export const Observer = props => {
const nameRef = useRef();
const reaction = useRef(null);
const [_, setTick] = useState({});
const update = useCallback(vaz => {
setTick(old => {
return { ...old, ...vaz };
});
}, []);
useEffect(() => {
console.log("EFFECT");
return () => {
console.log("UNMOUNTED");
const name = nameRef.current;
const objects = componentToObjects[name];
if (objects !== undefined) {
componentToObjects[name] = undefined;
objects.forEach(component => {
objectToComponents[component._id] = undefined;
});
}
};
}, []);
if (!reaction.current) {
console.log("ONCE");
nameRef.current = `observer(${getNextId()})`;
const name = nameRef.current;
reaction.current = {
name: name,
invalidate: newState => {
// or update(newState)
setTick(old => {
return { ...old, ...newState };
});
}
};
}
currentlyRenderingComponent = reaction;
console.log("render");
return props.children();
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment