Why even bother?
Because building things from scratch — even tiny versions — makes you understand how they actually work.
Step 1: Creating a Central Store
State Management is basically one central place to keep your app’s state. Let’s start by building that.
const createStore = (initialStore) => {
let state = { ...initialStore };
const listeners = [];
const getState = () => state;
const setState = (newState) => {
state = { ...state, ...newState };
listeners.forEach((listener) => listener(state));
};
const subscribe = (listener) => {
listeners.push(listener);
return () => {
const index = listeners.indexOf(listener);
listeners.splice(index, 1);
};
};
return { getState, setState, subscribe };
};
// create a store
const store = createStore({ user: null, theme: "light" });
Now we have a store with three key features:
getState()
→ returns the current statesetState()
→ updates the state and notifies subscriberssubscribe()
→ lets you listen for changes
Step 2: Connecting Components
Next, I wanted the UI to update automatically when state changes. Since I’m not using React here, let’s do it in plain JS.
const renderUser = () => {
const { user } = store.getState();
document.getElementById("user").textContent =
user ? `Hello, ${user}` : "No user found";
};
// subscribe to state changes
store.subscribe(renderUser);
// trigger initial render
renderUser();
Now whenever I call store.setState({ user: "Ahad" })
, the UI updates itself. Nice.
Step 3: Adding Actions
Updating state directly works, but it gets messy. So I wrapped updates in actions — just like Redux.
const actions = {
login: (userName) => store.setState({ user: userName }),
logout: () => store.setState({ user: null }),
toggleTheme: () => {
const { theme } = store.getState();
store.setState({ theme: theme === "light" ? "dark" : "light" });
},
};
Now we can call:
actions.login("Hritik");
actions.logout();
Cleaner and more predictable.
Step 4: Making the DOM Reactive
Let’s go one step further and bind DOM elements directly to the store.
const bindTextContent = (elementId, selector) => {
const render = () => {
const state = store.getState();
document.getElementById(elementId).textContent = selector(state);
};
store.subscribe(render);
render();
};
Usage:
bindTextContent("userName", (state) => state.user || "no user");
bindTextContent("theme", (state) => `Current theme: ${state.theme}`);
Now your DOM updates whenever the store changes — no frameworks needed.
What I Learned
- State management looks simple at first. But as apps grow, it becomes the glue that holds everything together.
- Redux isn’t magic. Under the hood, it’s just patterns like immutability and observer/observable.
- Building your own tools makes you smarter. You’ll never see Redux the same way again.
- The DOM can be reactive — even without a framework. Just smart subscriptions and bindings are enough.