Stateful Components in Vanilla JS
Today I’d like to show you how to create stateful components with vanilla JavaScript 🍦, and demonstrate that:
You don’t need to download any library to apply the amazing concepts used by React.js in your JavaScript programming.
Yes, it’s now entirely possible to use the powerful features of JavaScript out-of-the-box to do some pretty amazing things. Let’s dive right in!
Demo
To see the final version of what we are building, check out this JS Fiddle ↗️
Step 1
Let’s start with a simple object, which will represent the initial state of our data:
let state = {
count: 0,
};
From here, I will create several methods, which I will divide into 2 categories:
- Component functions
- Render functions
We will use ES6 template strings (See: Template Literals ↗️) to construct our components, which can be fed data and react dynamically to user actions.
Render functions will be called to display the component when the user triggers changes.
Step 2
First let’s create our counter component, which is just an arrow function that returns a template string.
const counter = (count) => `<div class="counter">${count}</div>`;
Disclaimer: When passing values to template strings, you must be very certain that the data you pass to your functions is secure, since JS won’t automatically escape these values like they do in React.
Be mindful of XSS when you do this by either sanitizing any values or making sure the values passed are only ones that you control.
Step 3
Next, let’s create a render function, which we can call to update the DOM when a user triggers a change to the count.
renderCount = () => {
document.getElementById('app').innerHTML = counter(state.count);
};
Step 4
Now, all we need is a method to update the count. Since we don’t have a setState({})
method built-in to JavaScript just yet, we will do a React no-no, and set the state directly 🙀:
incrementCountUp = () => {
let newCount = state.count + 1;
state.count = newCount;
};
Step 5
Then we can attach an event listener to a button click.
When the user clicks the button, the count will increment up by updating the value of state.count
. After that, we can call the render method so we will instantly see the change rendered in the DOM:
const incCountBtn = document.querySelector("[data-action='count-up']");
incCountBtn.click(() => {
incrementCountUp();
renderCount();
});
And with that, we have a stateful component created with plain-old vanilla JS!
🍦🍦🍦
Bonus material
Just like in React.js, we can utilize JS logic within our components to modify the frontend user experience.
For example, we can change the class name in use by checking the value of count:
const counter = (count) => {
let className = "text-error";
if (count > 10) {
classname = "text-success";
}
return `<div class="${className}">${count}</div>;
}
And in our CSS:
.text-error {
color: red;
}
.text-success {
color: green;
}
Now, when we increment our count past 10, the color of the text will automatically change, without having to do any extra DOM queries or wacky jQuery stuff.
Conclusion
As you can see, it is very handy to program in JavaScript this way, using the powerful features built-in to the syntax. Even though we can’t benefit from React’s virtual DOM
or setState({})
method, this is a powerful way to think and program — and there is no external dependency to worry about!
What are the benefits?
- Write organized, reactive code without the increased bundle size
- Simplify DOM manipulations and state logic
- Improve the versatility and strength of your standard JavaScript coding!
Resources
Demo
Source code
HTML
<div class="App__container" id="app">
<div>Count: <span id="display">0</span></div>
<button onclick="incCountUp()">+Count</button>
</div>
JS
const display = document.getElementById('display');
const state = {
count: 0,
};
// Components
const counter = (count) => {
let classname = 'text-error';
if (count >= 5) {
classname = 'text-success';
}
return `<span class="${classname}">${count}</span>`;
};
// Render Methods
function renderCount() {
display.innerHTML = counter(state.count);
}
// Update methods
function incCountUp() {
let newCount = state.count + 1;
state.count = newCount;
renderCount();
}
renderCount();
CSS
h1,
h2,
h3,
h4,
h5,
h6,
p,
ul,
ol {
margin-top: 0;
}
.App__container {
padding: 15px;
}
.Score {
font-size: 28px;
}
.text-error {
color: red;
}
.text-success {
color: green;
font-size: 40px;
}