Skip to content

Instantly share code, notes, and snippets.

@sergiodxa
Last active September 22, 2023 21:05
Show Gist options
  • Save sergiodxa/a493c98b7884128081bb9a281952ef33 to your computer and use it in GitHub Desktop.
Save sergiodxa/a493c98b7884128081bb9a281952ef33 to your computer and use it in GitHub Desktop.
Example implementation of the same API of React.createElement but using native DOM elements
// use JSX with el instead of React.createElement
/** @jsx createElement */
const Children = {
only(children) {
if (children.length > 1 || children.length === 0) {
throw new Error('The children must have only one element');
}
return children[0];
}
}
// check if it's an event if it starts with a `on` and followed with a capitalized character (eg. onClick)
function isEvent(attr) {
return (attr.startsWith('on') && attr[2].toLowerCase() !== attr[2])
}
// get the name of the event without the `on` (eg. click instead of onClick)
function getEventName(attr) {
return attr.split('').splice(2).join('').toLowerCase();
}
// our element factory
function createElement(type, _props, ..._children) {
// define a default value for props
const props = _props || {};
// if _children is an array of array take the first value, else take the full array
const children = Array.isArray(_children[0]) ? _children[0] : _children;
// if type is a function run it passing the props and children
if (typeof type === 'function') {
// merge props and children
const componentProps = { ...props, children: (props.children || []).concat(children) };
// if it's a classy component
if (type.isClass) {
// instance it
const component = new type(componentProps);
// render it
const element = component.render(componentProps);
return element;
}
// render the function
const element = type(componentProps);
return element;
}
// create a new DOM element
const element = document.createElement(type);
// set each attribute (prop), if it's children append it, if it's a event handler start listening
Object.entries(props).forEach(([name, value]) => {
if (name === 'children') {
return value
.map(child => typeof child === 'string' ? document.createTextNode(child) : child)
.forEach(child => element.appendChild(child));
}
if (!isEvent(name)) {
return element.setAttribute(name, value);
}
const eventName = getEventName(name);
element.addEventListener(eventName, value);
})
// append children
children
.map(child => typeof child === 'string' ? document.createTextNode(child) : child)
.forEach(child => element.appendChild(child));
return element;
}
// class used to create components with an internal state
class Component {
constructor(props) {
this.state = this.getInitialState(props);
}
getInitialState() {
return {};
}
// update the state and call onUpdate
setState(handler) {
if (typeof handler !== 'function') {
this.state = { ...this.state, ...handler };
if (!this.onUpdate) return this.state;
return this.onUpdate(this.state);
}
this.state = handler(this.state)
if (!this.onUpdate) return this.state;
return this.onUpdate(this.state);
}
render() {
throw new ReferenceError('You must define your own render function.');
}
}
Component.isClass = true;
// render our DOM tree in a target element (default to the the body)
function render(element, target = document.body) {
// clear old HTML
target.innerHTML = '';
// add the new HTML
target.appendChild(element);
// return the target
return target;
}
// basic pure component
function TitleContent({ name = '' } = {}) {
return <span>hola {name}</span>
}
// custom classy component
class Title extends Component {
constructor(props) {
super(props);
this.updateName = this.updateName.bind(this);
}
getInitialState({ name = '' } = {}) {
return { name };
}
onUpdate(state) {
render(
<TitleContent name={state.name} />,
this.el
);
}
updateName() {
this.setState(state => ({ oldName: state.name, name: state.oldName || 'mundo' }))
}
render(props) {
this.el = (
<h1 onClick={this.updateName}>
<TitleContent name={this.state.name} />
</h1>
);
return this.el;
}
}
// pure app wrapper component
function App() {
return (
<div id="hola">
<Title />
<Title name="world" />
<Title />
<Title />
<Title />
</div>
);
}
// render our app
render(<App />);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment