Skip to content

A React-inspired JavaScript framework built from scratch without using any existing libraries or frameworks. This framework implements core frontend concepts including Virtual DOM, component-based architecture, state management with hooks, and client-side routing.

Notifications You must be signed in to change notification settings

yelmach/mini-react-framework

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

34 Commits
 
 
 
 
 
 

Repository files navigation

Custom MiniReact JavaScript Framework

Overview

A lightweight, React-inspired JavaScript framework built from scratch without using any existing libraries or frameworks. This framework implements core frontend concepts including Virtual DOM, component-based architecture, state management with hooks, and client-side routing. The project demonstrates a deep understanding of how modern JavaScript frameworks work under the hood.

Table of Contents

Features

🎨 Virtual DOM Implementation

  • Efficient Diffing Algorithm: Smart DOM reconciliation that updates only what changed
  • Fragment Support: Render multiple elements without wrapper nodes
  • Keyed Elements: Optimized list rendering with key-based reconciliation
  • Text Node Optimization: Efficient handling of text content updates

🪝 React-like Hooks System

  • useState: Manage component state with automatic re-rendering
  • useEffect: Handle side effects with dependency tracking and cleanup
  • useRef: Create mutable references that persist across renders

🧩 Component Architecture

  • Functional Components: Pure functions that return virtual DOM nodes
  • Props System: Pass data and callbacks between components
  • Conditional Rendering: Dynamic UI based on application state
  • List Rendering: Efficiently render dynamic lists with proper key handling

🛣️ Client-Side Routing

  • Hash-based Navigation: URL synchronization with application state
  • Route Handling: Navigate between views without page reloads
  • History Integration: Browser back/forward button support

⚡ Performance Optimizations

  • Minimal DOM Manipulation: Only update changed elements
  • Batched Updates: Group state changes for efficient rendering
  • Effect Scheduling: Run effects asynchronously to avoid blocking

Architecture

The framework follows a unidirectional data flow pattern similar to React:

User Action → State Update → Virtual DOM Creation → Diffing → DOM Update

Core Modules

src/
├── core/
│   ├── vdom.js          # Virtual DOM creation and rendering
│   ├── diffing.js       # Reconciliation algorithm
│   └── hooks.js         # State management hooks
└── index.js             # Public API exports

Getting Started

Basic Usage

import { render, createElement, useState } from './src/index.js';

function Counter() {
    const [count, setCount] = useState(0);

    return {
        tag: 'div',
        attrs: {},
        children: [
            {
                tag: 'h1',
                attrs: {},
                children: [`Count: ${count}`]
            },
            {
                tag: 'button',
                attrs: {
                    onClick: () => setCount(count + 1)
                },
                children: ['Increment']
            }
        ]
    };
}

// Mount the app
render(Counter, document.getElementById('app'));

Running the TodoMVC Demo

# Navigate to the TodoMVC directory
cd todoMVC

# Open index.html in your browser
# Or use a local server (recommended)
python -m http.server 8000
# Navigate to http://localhost:8000

Core Concepts

1. Virtual DOM Structure

The framework represents UI as JavaScript objects:

// HTML: <div class="container"><h1>Hello</h1></div>

// Virtual DOM representation:
{
    tag: 'div',
    attrs: { class: 'container' },
    children: [
        {
            tag: 'h1',
            attrs: {},
            children: ['Hello']
        }
    ]
}

2. Creating Elements

// Simple element
{
    tag: 'button',
    attrs: {
        class: 'btn',
        onClick: handleClick
    },
    children: ['Click me']
}

// Nested elements
{
    tag: 'ul',
    attrs: { class: 'list' },
    children: [
        { tag: 'li', attrs: {}, children: ['Item 1'] },
        { tag: 'li', attrs: {}, children: ['Item 2'] }
    ]
}

3. Event Handling

Events are attached using camelCase attribute names:

{
    tag: 'input',
    attrs: {
        type: 'text',
        onKeyDown: (e) => {
            if (e.key === 'Enter') {
                console.log('Enter pressed!');
            }
        },
        onChange: (e) => console.log(e.target.value)
    },
    children: []
}

4. Conditional Rendering

Use JavaScript expressions to conditionally render elements:

function Component({ isLoggedIn }) {
    return {
        tag: 'div',
        attrs: {},
        children: [
            isLoggedIn ? {
                tag: 'p',
                attrs: {},
                children: ['Welcome back!']
            } : {
                tag: 'p',
                attrs: {},
                children: ['Please log in']
            }
        ]
    };
}

5. List Rendering with Keys

For dynamic lists, use the key attribute for optimal performance:

function TodoList({ todos }) {
    return {
        tag: 'ul',
        attrs: {},
        children: todos.map(todo => ({
            tag: 'li',
            attrs: {
                key: todo.id  // Important for efficient updates
            },
            children: [todo.text]
        }))
    };
}

API Reference

render(component, container)

Mounts the root component to a DOM element.

render(App, document.getElementById('root'));

Parameters:

  • component: Function that returns a virtual DOM node
  • container: DOM element to mount the app

useState(initialValue)

Creates a stateful value that triggers re-renders when updated.

const [count, setCount] = useState(0);

// Update state
setCount(count + 1);

// Functional update
setCount(prevCount => prevCount + 1);

Returns: Array with current state and setter function


useEffect(callback, dependencies)

Runs side effects after render. Cleanup functions are called before re-running or unmounting.

// Run on every render
useEffect(() => {
    console.log('Component rendered');
});

// Run once on mount
useEffect(() => {
    console.log('Component mounted');
}, []);

// Run when dependencies change
useEffect(() => {
    console.log('Count changed:', count);
}, [count]);

// With cleanup
useEffect(() => {
    const timer = setInterval(() => console.log('tick'), 1000);
    return () => clearInterval(timer);  // Cleanup
}, []);

Parameters:

  • callback: Function to run (can return cleanup function)
  • dependencies: Array of values to watch (optional)

useRef(initialValue)

Creates a mutable reference that persists across renders.

const inputRef = useRef(null);

// Access the ref
useEffect(() => {
    inputRef.current.focus();
}, []);

return {
    tag: 'input',
    attrs: { ref: inputRef },
    children: []
};

Returns: Object with current property


unmount()

Unmounts the current app and cleans up effects.

import { unmount } from './src/index.js';

unmount();

TodoMVC Demo

The framework includes a fully functional TodoMVC implementation showcasing:

  • ✅ Add, edit, and delete todos
  • ✅ Mark todos as complete/incomplete
  • ✅ Filter todos (All, Active, Completed)
  • ✅ Clear completed todos
  • ✅ Toggle all todos
  • ✅ Persistent todo count
  • ✅ URL routing with hash-based navigation
  • ✅ Double-click to edit functionality
  • ✅ Keyboard shortcuts (Enter to save)

TodoMVC Structure

todoMVC/
├── app.js                      # Main application logic
├── components/
│   ├── todoHeader.js          # Input for new todos
│   ├── todoList.js            # List of todos with items
│   └── todoFooter.js          # Filter buttons and counters
├── index.html                 # Entry point
└── styles.css                 # TodoMVC styles

What I Learned

This project provided deep insights into frontend framework internals:

Framework Architecture

  • Virtual DOM Implementation: Built a complete diffing algorithm that efficiently updates the real DOM
  • Reconciliation: Implemented keyed reconciliation for optimal list rendering performance
  • Component Lifecycle: Created a system for mounting, updating, and unmounting components

Advanced JavaScript

  • Closures: Leveraged closures for hook state management and effect tracking
  • Event Delegation: Implemented efficient event handling without memory leaks
  • Algorithm Design: Developed diffing algorithms to minimize DOM operations
  • Module System: Organized code with ES6 modules and clean separation of concerns

State Management

  • Hook Implementation: Built useState, useEffect, and useRef from scratch
  • Dependency Tracking: Implemented dependency arrays for effect optimization
  • Batch Updates: Grouped state changes to prevent unnecessary re-renders
  • Effect Cleanup: Managed side effect lifecycle and cleanup functions

DOM Manipulation

  • Performance Optimization: Minimized reflows and repaints through smart diffing
  • Fragment Handling: Implemented fragment rendering for cleaner component trees
  • Attribute Management: Built efficient attribute updating without full replacement
  • Text Node Optimization: Optimized text content updates

Routing & Navigation

  • Hash-based Routing: Implemented client-side navigation with URL synchronization
  • State-URL Sync: Kept application state in sync with browser URL
  • History API: Integrated with browser history for back/forward navigation

Software Design Principles

  • Inversion of Control: Understood the framework-vs-library distinction
  • API Design: Created intuitive, React-like APIs for developers
  • Documentation: Wrote comprehensive documentation for framework users
  • Testing: Debugged complex asynchronous rendering and state updates

Problem Solving

  • Edge Cases: Handled conditional rendering, null values, and nested fragments
  • Performance: Optimized rendering for large lists with proper key handling
  • Memory Management: Prevented memory leaks in event handlers and effects
  • Browser Compatibility: Ensured cross-browser DOM manipulation

Technologies

  • Language: Vanilla JavaScript (ES6+)
  • Module System: ES6 Modules
  • DOM API: Native browser APIs
  • No External Dependencies: Built entirely from scratch

Technical Highlights

Diffing Algorithm Features

  • Minimal DOM operations through smart reconciliation
  • Support for keyed lists for optimal performance
  • Fragment support for cleaner component trees
  • Efficient text node updates
  • Proper cleanup of removed elements

Hook System Features

  • State persistence across renders
  • Effect dependency tracking
  • Cleanup function support
  • Multiple hooks per component
  • Predictable execution order

Routing Features

  • Hash-based URL routing
  • State synchronization with URL
  • Browser history integration
  • Programmatic navigation support

Why This Approach?

This framework uses Virtual DOM with Diffing because:

  1. Performance: Only updates changed parts of the DOM
  2. Declarative: Developers describe what the UI should look like, not how to change it
  3. Predictable: Unidirectional data flow makes debugging easier
  4. Familiar: API similar to React makes it approachable

Alternative approaches considered:

  • Data Binding: Two-way synchronization (Angular-style)
  • Template Literals: String-based templating
  • Direct DOM: Imperative manipulation

Virtual DOM offered the best balance of performance, developer experience, and maintainability.

About

A React-inspired JavaScript framework built from scratch without using any existing libraries or frameworks. This framework implements core frontend concepts including Virtual DOM, component-based architecture, state management with hooks, and client-side routing.

Topics

Resources

Stars

Watchers

Forks