const { createElement } = require("react");
const React = {
states: new Map(),
stateIndex: 0,
effects: new Map(),
effectIndex: 0,
cleanups: new Map(),
updateQueue: new Set(),
currentComponent: null,
isRendering: false,
pendingUpdate: false,
createElement(type, props, ...children) {
return {
type,
props: {
...props,
children: children.flat().filter(Boolean).map(child =>
typeof child === 'object' ? child : String(child)
),
},
};
},
render(component, container) {
if (!container) {
console.error('Container is required');
return;
}
this.resetIndexes();
this.currentComponent = component;
const vnode = typeof component === 'function' ? component() : component;
this.currentComponent = null;
const dom = this.createDOM(vnode);
if (container && container.nodeType === 1) {
container.innerHTML = "";
container.appendChild(dom);
}
this.runEffects(component);
const update = () => {
this.scheduleUpdate(component, container);
};
if (!this.updaters) this.updaters = new Map();
this.updaters.set(component, { update, container });
return update;
},
scheduleUpdate(component, container) {
if (this.isRendering) {
this.pendingUpdate = true;
return;
}
this.updateQueue.add(component);
Promise.resolve().then(() => {
if (this.updateQueue.size > 0) {
const components = Array.from(this.updateQueue);
this.updateQueue.clear();
components.forEach(comp => {
const updater = this.updaters?.get(comp);
if (updater) {
this.performUpdate(comp, updater.container);
}
});
}
});
},
performUpdate(component, container) {
this.isRendering = true;
this.resetIndexes();
this.currentComponent = component;
const vnode = component();
this.currentComponent = null;
this.cleanupEffects(component);
const dom = this.createDOM(vnode);
if (container && container.nodeType === 1) {
const oldDom = container.firstChild;
if (oldDom && oldDom.parentNode) {
container.replaceChild(dom, oldDom);
} else {
container.innerHTML = "";
container.appendChild(dom);
}
}
this.runEffects(component);
this.isRendering = false;
if (this.pendingUpdate) {
this.pendingUpdate = false;
this.scheduleUpdate(component, container);
}
},
resetIndexes() {
this.stateIndex = 0;
this.effectIndex = 0;
},
createDOM(vnode) {
if (typeof vnode === "string" || typeof vnode === "number") {
return document.createTextNode(vnode);
}
if (vnode == null || vnode === false) {
return document.createTextNode('');
}
const { type, props } = vnode;
if (typeof type === 'function') {
return this.createDOM(type(props));
}
const dom = document.createElement(type);
for (const key in props) {
if (key === "children") continue;
if (key.startsWith("on") && typeof props[key] === 'function') {
const eventName = key.slice(2).toLowerCase();
dom.addEventListener(eventName, props[key]);
} else if (key === 'className') {
dom.setAttribute('class', props[key]);
} else if (key === 'style' && typeof props[key] === 'object') {
Object.assign(dom.style, props[key]);
} else {
dom.setAttribute(key, props[key]);
}
}
if (props.children) {
props.children.forEach((child) => {
if (child != null) {
dom.appendChild(this.createDOM(child));
}
});
}
return dom;
},
cleanupEffects(component) {
const componentEffects = this.effects.get(component) || [];
const componentCleanups = this.cleanups.get(component) || [];
componentCleanups.forEach(cleanup => {
if (typeof cleanup === 'function') {
try {
cleanup();
} catch (error) {
console.error('Error in cleanup function:', error);
}
}
});
if (this.cleanups.has(component)) {
this.cleanups.set(component, []);
}
},
runEffects(component) {
const componentEffects = this.effects.get(component) || [];
const newCleanups = [];
componentEffects.forEach((effect, index) => {
const { callback, dependencies } = effect;
let hasChanged = true;
if (dependencies) {
const prevEffect = componentEffects[index];
if (prevEffect && prevEffect.prevDependencies) {
hasChanged = dependencies.some((dep, i) =>
!Object.is(dep, prevEffect.prevDependencies[i])
);
}
}
if (hasChanged) {
const cleanup = callback();
if (typeof cleanup === 'function') {
newCleanups.push(cleanup);
}
effect.prevDependencies = dependencies;
}
});
this.cleanups.set(component, newCleanups);
},
};
function useState(initialValue) {
const component = React.currentComponent;
if (!component) {
throw new Error("useState must be called within a component");
}
if (!React.states.has(component)) {
React.states.set(component, []);
}
const componentStates = React.states.get(component);
const currentIndex = React.stateIndex;
if (componentStates[currentIndex] === undefined) {
componentStates[currentIndex] =
typeof initialValue === 'function'
? initialValue()
: initialValue;
}
const state = componentStates[currentIndex];
const setState = (newState) => {
const currentValue = componentStates[currentIndex];
if (typeof newState === 'function') {
newState = newState(currentValue);
}
if (!Object.is(currentValue, newState)) {
componentStates[currentIndex] = newState;
const updater = React.updaters?.get(component);
if (updater) {
React.scheduleUpdate(component, updater.container);
}
}
};
React.stateIndex++;
return [state, setState];
}
function useEffect(callback, dependencies) {
const component = React.currentComponent;
if (!component) {
throw new Error("useEffect must be called within a component");
}
if (!React.effects.has(component)) {
React.effects.set(component, []);
}
const componentEffects = React.effects.get(component);
const currentIndex = React.effectIndex;
componentEffects[currentIndex] = {
callback,
dependencies,
prevDependencies: null,
};
React.effectIndex++;
}
module.exports = { React, useState, useEffect};