A tiny library (less than 50 lines of code) that brings React-like hooks
to vanilla JavaScript.
View source code on GitHub
The first thing I did was create a global variable to prevent polluting the window object.
vanilla-hooks.js
window.VanillaHooks = {};
Next, I added some properties and methods to manage states and effects.
vanilla-hooks.js
window.VanillaHooks = {
states: [],
State: class {},
useState: () => {},
useEffect: () => {},
};
The constructor on the State class initializes the value and pushes an event listener to the states array.
vanilla-hooks.js
constructor(intialValue) {
this.value = intialValue;
const { length: index } = window.VanillaHooks.states;
this.id = `vanilla-state-${index}`;
window.VanillaHooks.states.push(new Event(this.id));
this.event = window.VanillaHooks.states[index];
}
Within useState, I have a setState function that dispatches the event when the state changes.
vanilla-hooks.js
const setState = (parameter) => {
const isFunction = typeof parameter === "function";
const value = isFunction ? parameter(state.value) : parameter;
state.set(value);
dispatchEvent(state.event);
};
Finally, the useEffect method adds an event listener using the callback for all the dependencies.
vanilla-hooks.js
dependencies.forEach((state) => addEventListener(state.id, callback));
Here are some practical examples of using Vanilla Hooks:
This example allows users to increment and decrement a counter.
index.html
<button onclick="handleDecrement()">Decrement</button>
<span id="count"></span>
<button onclick="handleIncrement()">Increment</button>
app.js
const { useEffect, useState } = VanillaHooks;
const countElement = document.getElementById("count");
const [count, setCount] = useState(1);
const handleIncrement = () => setCount((previousState) => ++previousState);
const handleDecrement = () => setCount((previousState) => --previousState);
useEffect(() => {
countElement.innerText = count;
}, [count]);
This example binds an input field to a state variable and displays the value in real-time.
Name:
index.html
<p>
<span><strong>Name: </strong></span>
<span id="user-text"></span>
</p>
<input type="text" id="user-input" onkeyup="handleUserChange(event)" />
app.js
const { useEffect, useState } = VanillaHooks;
const userTextElement = document.getElementById("user-text");
const userInputElement = document.getElementById("user-input");
const [user, setUser] = useState("Jane Doe");
const handleUserChange = (event) => setUser(event.target.value);
useEffect(() => {
userTextElement.innerText = user;
userInputElement.value = user;
}, [user]);
This example manages a list of items and allows a user to add/remove tasks.
index.html
<ul id="todo-list"></ul>
<input type="text" id="todo-input" />
<button id="add-todo-button">Add Todo</button>
app.js
const { useEffect, useState } = VanillaHooks;
const todoListElement = document.getElementById("todo-list");
const todoInputElement = document.getElementById("todo-input");
const addTodoButtonElement = document.getElementById("add-todo-button");
const [items, setItems] = useState(["Write more JavaScript"]);
addTodoButtonElement.addEventListener("click", () =>
setItems((previousState) => [...previousState, todoInputElement.value])
);
useEffect(() => {
todoListElement.innerHTML = "";
todoInputElement.value = "";
items.map((item, index) => {
const todoItem = document.createElement("li");
const removeItemButtonElement = document.createElement("button");
removeItemButtonElement.innerText = "Remove";
removeItemButtonElement.addEventListener("click", () => {
setItems((previousState) => {
previousState.splice(index, 1);
return previousState;
});
});
todoItem.innerText = item;
todoItem.appendChild(removeItemButtonElement);
todoListElement.appendChild(todoItem);
});
}, [items]);