Projects About me Blog FAQ Contact me

Vanilla JavaScript Patterns - I Use Instead of React,...

My site doesn't need a 100KB framework. Here's how I handle state, components, and routing with pure JS.

July 20, 2026 · BLOG
Vanilla JavaScript Patterns - I Use Instead of React,...

Vanilla JavaScript Patterns I Use Instead of React

My portfolio is a PHP site with sprinkles of JavaScript—not a SPA. But I still need interactivity. Here's my toolkit.

1. The Store Pattern (No Redux Needed)

// js/store.js
class Store {
    constructor(initialState = {}) {
        this.state = initialState;
        this.listeners = [];
    }
    
    getState() {
        return this.state;
    }
    
    setState(newState) {
        this.state = { ...this.state, ...newState };
        this.listeners.forEach(fn => fn(this.state));
    }
    
    subscribe(listener) {
        this.listeners.push(listener);
        return () => {
            this.listeners = this.listeners.filter(l => l !== listener);
        };
    }
}

// Usage
const themeStore = new Store({ theme: 'light' });

themeStore.subscribe(({ theme }) => {
    document.documentElement.dataset.theme = theme;
    localStorage.setItem('theme', theme);
});

// Toggle theme
document.querySelector('#theme-toggle').addEventListener('click', () => {
    const current = themeStore.getState().theme;
    themeStore.setState({ theme: current === 'light' ? 'dark' : 'light' });
});

2. Web Components for Reusability

// js/components/BlogCard.js
class BlogCard extends HTMLElement {
    connectedCallback() {
        const { title, excerpt, date, slug } = this.dataset;
        
        this.innerHTML = `
            <article class="card">
                <h3>${title}</h3>
                <time>${new Date(date).toLocaleDateString()}</time>
                <p>${excerpt}</p>
                <a href="/post.php?slug=${slug}">Read more</a>
            </article>
        `;
    }
}

customElements.define('blog-card', BlogCard);
<!-- Usage in PHP -->
<blog-card 
    data-title="<?= htmlspecialchars($post['title']) ?>"
    data-excerpt="<?= htmlspecialchars($post['excerpt']) ?>"
    data-date="<?= $post['date'] ?>"
    data-slug="<?= $post['slug'] ?>">
</blog-card>

3. Fetch with Error Handling

// js/api.js
async function fetchJSON(url, options = {}) {
    try {
        const response = await fetch(url, {
            headers: { 'Accept': 'application/json' },
            ...options
        });
        
        if (!response.ok) {
            throw new Error(`HTTP ${response.status}`);
        }
        
        return await response.json();
    } catch (error) {
        console.error(`Failed to fetch ${url}:`, error);
        // Return cached data or fallback
        return JSON.parse(localStorage.getItem(`cache_${url}`) || 'null');
    }
}

// Usage: Load more posts dynamically
let currentPage = 1;
async function loadMorePosts() {
    const posts = await fetchJSON(`/api/posts.php?page=${++currentPage}`);
    if (posts) {
        posts.forEach(post => {
            const card = document.createElement('blog-card');
            card.dataset = post; // Simplified
            document.querySelector('.blog-grid').appendChild(card);
        });
    }
}

4. Event Delegation (Because PHP Renders HTML)

Since my HTML comes from PHP, I can't add listeners during render. Solution:

document.addEventListener('click', (e) => {
    // Handle all "Read More" clicks
    if (e.target.matches('.read-more')) {
        e.preventDefault();
        const slug = e.target.dataset.slug;
        loadPostModal(slug);
    }
    
    // Handle dynamic elements too
    if (e.target.closest('.delete-post')) {
        const id = e.target.closest('.delete-post').dataset.id;
        deletePost(id);
    }
});

The Bundle Size?

2.3KB minified. No build step. No node_modules. Just modern JavaScript.

You don't need React for a blog. You need good patterns.

More from the blog