# Comparing JavaScript UI Paradigms: From Imperative to Reactive Modern web development offers multiple approaches to building user interfaces. In this post, I'll compare four different implementations of the same simple application: a page with a header, a list of pioneering women in computing, and a "like" button that tracks clicks. Each implementation achieves the same result but uses fundamentally different programming paradigms. Understanding these differences can help you choose the right approach for your projects and better appreciate the evolution of web development. ## The Four Approaches 1. **Vanilla JS (Imperative)**: Direct DOM manipulation with procedural code 2. **Vanilla JS (Functional)**: Pure functions and immutability principles 3. **React (Browser-loaded)**: Component-based with hooks for state management 4. **Next.js**: Modern React framework with server components Let's examine each approach side by side:
Vanilla JS (Imperative)
Vanilla JS (Functional)
React
Next.js
Imperative Approach Score: 9/10
<!-- 
 Imperative Target: direct, procedural 
 Imperative Score: 9/10 |--------x-|
 Imperative Patterns Exhibited in Code Below:
    - Buzzwords:
        - Direct DOM manipulation
        - Procedural programming
    - Rendering Strategy:
        - One-time direct rendering
        - Updates are imperatively targeted to DOM API
    - Function Abstraction:
        - Helper functions primarily for organization
    - State Management:
        - Mutable state, passed as direct references
    - Separation of Concerns:
        - Less separation between state, UI, and event handling
    - Event Handling:
        - Events trigger state modifications directly
 Imperative Anti-Patterns in Code Below:
    - Direct passing of state as args is an anti-pattern, but my preference
 Orthogonal to Imperative-ness, My Preference:
    - Global state
    - Separating data and behavior
 Call it: "Imperative approach with helper functions"
-->
<html>
    <body>
        <div id = 'app'></div>
        <script>
            // Helper functions
            function createHeader(title) {
                const header = document.createElement('h1');
                header.textContent = title;
                return header;
            }
            function createList(names) {
                const ul = document.createElement('ul');
                names.forEach(name => {
                    const li = document.createElement('li');
                    li.textContent = name;
                    ul.appendChild(li);
                });
                return ul;
            }
            function createButton(text, data, click_handler) {
                const button = document.createElement('button');
                button.textContent = text;
                button.addEventListener('click', function() {
                    click_handler(data, button);
                });
                return button;
            }

            // Event handlers
            function onClickIncLike(data, button) {
                data.likeCount++;
                button.textContent = `${appData.buttonText} (${data.likeCount})`;
            }

            // State
            const appData = {
                title: 'Vanilla JS (Imperative) Version',
                items: ['Ada Lovelace', 'Grace Hopper', 'Margaret Hamilton'],
                buttonText: 'Like',
                likeCount: 0
            };

            // Imperative rendering
            function HomePage(data) {
                const app = document.getElementById('app');
                app.appendChild(createHeader(data.title));
                app.appendChild(createList(data.items));
                app.appendChild(createButton(
                    `${data.buttonText} (${data.likeCount})`,
                    data,
                    onClickIncLike
                ));
            }
            HomePage(appData);
        </script>
    </body>
</html>
Functional Approach Score: 7/10
<!-- 
 Functional Target: pure, composable, immutable
 Functional Score: 7/10 |------x---|
 Functional Patterns Exhibited in Code Below:
    - Buzzwords:
        - Pure functions (no side effects)
        - Composition (nesting functions)
    - Rendering Strategy:
        - Explicit re-rendering, since full state is passed to render function
        - Whole DOM tree is re-created on each update
    - Function Abstraction:
        - Helper functions represent higher-order components, not just organizational helpers
    - State Management:
        - State passed directly to functions via arguments
        - Preserves immutability by never modifying the existing state
        - Treats state as a series of values over time rather than a single value that changes
    - Separation of Concerns:
        - More separation between state, UI, and event handling
    - Event Handling:
        - Events trigger new state creation, not direct state modifications
 Functional Anti-Patterns in Code Below:
    - Side effects persist (anti-pattern), although can't avoid since DOM environment is inherently impure
 Orthogonal to Functional-ness, My Preference:
    - Global state
    - Separating data and behavior
 Call it: "Pragmatic functional approach adapted to the DOM environment"
-->
<html>
    <body>
        <div id='app'></div>
        <script>
            // Create a pure function to generate DOM elements
            function createElement(type, props = {}, ...children) {
                const element = document.createElement(type);
                
                // Apply properties
                Object.entries(props).forEach(([key, value]) => {
                    if (key === 'eventListeners') {
                        // Handle event listeners
                        Object.entries(value).forEach(([event, handler]) => {
                            element.addEventListener(event, handler);
                        });
                    } else if (key === 'style') {
                        // Handle style objects
                        Object.assign(element.style, value);
                    } else {
                        // Handle regular attributes
                        element[key] = value;
                    }
                });
                
                // Append children
                children.flat().forEach(child => {
                    if (child instanceof Node) {
                        element.appendChild(child);
                    } else if (child !== null && child !== undefined) {
                        element.appendChild(document.createTextNode(child));
                    }
                });
                
                return element;
            }

            // Component functions that return DOM elements
            function Header(title) {
                return createElement('h1', {}, title);
            }
            function ListItem(text) {
                return createElement('li', {}, text);
            }
            function List(items) {
                return createElement('ul', {}, 
                    ...items.map(item => ListItem(item))
                );
            }
            function Button(text, onClick, count = null) {
                const buttonText = count !== null ? `${text} (${count})` : text;
                return createElement('button', {
                    eventListeners: { click: onClick }
                }, buttonText);
            }

            // Event handlers
            function handleLikeClick(data) {
                // Create a new state object (keeping immutability)
                const newState = {
                    ...data,
                    likes: data.likes + 1
                };
                // Update the state (which triggers a re-render)
                updateState(newState);
            }

            // Rendering function --- necessarily impure given the DOM environment
            function renderApp(data, container) {
                const app = createElement('div', {},
                    Header(data.title),
                    List(data.items),
                    Button(data.buttonText, () => handleLikeClick(data), data.likes)
                );
                container.innerHTML = ''; // IMPURE: side effect
                container.appendChild(app); // IMPURE: side effect
            }

            // Simple state management
            let currentState = {
                title: 'Vanilla JS (Functional) Version',
                items: ['Ada Lovelace', 'Grace Hopper', 'Margaret Hamilton'],
                buttonText: 'Like',
                likes: 0
            };
            function updateState(newState) {
                currentState = newState;
                const container = document.getElementById('app');
                renderApp(currentState, container);
            }

            // Initialization when the DOM is loaded
            document.addEventListener('DOMContentLoaded', initApp);
            function initApp() {
                const container = document.getElementById('app');
                renderApp(currentState, container);
            }
        </script>
    </body>
</html>
React Approach
<!-- React! -->
<html>
    <head>
        <script src="https://unpkg.com/react@18/umd/react.development.js"></script>
        <script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
        <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
    </head>
    <body>
        <div id = 'app'></div>
        <script type = 'text/jsx'>

            function Header({title}) {
                console.log(title);
                return (
                    <h1>{title}</h1>
                );
            }

            function HomePage() {
                const names = ['Ada Lovelace', 'Grace Hopper', 'Margaret Hamilton'];
                const [likes, setLikes] = React.useState(0);

                function handleClick() {
                    setLikes(likes + 1);
                }

                return (
                    <div>
                        <Header title='React Version' />
                        <ul>
                            {names.map((name, index) => (
                                <li key={index}>{name}</li>
                            ))}
                        </ul>
                        <button onClick = {handleClick}>Like ({likes})</button>
                    </div>
                );
            }

            const domNode = document.getElementById('app');
            const root = ReactDOM.createRoot(domNode);
            root.render(<HomePage />);
        </script>
    </body>
</html>
Next.js Approach
// app/page.jsx
// NextJS!

////////// IMPORTS //////////
import { LikeButton } from './like-button';

////////// COMPONENTS //////////
function Header({title}) {
    console.log(title);
    return (
        <h1>{title}</h1>
    );
}

function HomePage() {
    const names = ['Ada Lovelace', 'Grace Hopper', 'Margaret Hamilton'];

    return (
        <div>
            <Header title='React Version' />
            <ul>
                {names.map((name, index) => (
                    <li key={index}>{name}</li>
                ))}
            </ul>
            <LikeButton />
        </div>
    );
}

////////// EXPORTS //////////
export default HomePage;
app/like-button.jsx
// NextJS!

////////// DIRECTIVES //////////
'use client';

////////// IMPORTS //////////
import { useState } from 'react';

////////// COMPONENTS //////////
function LikeButton() {

  const [likes, setLikes] = useState(0);
 
  function handleClick() {
    setLikes(likes + 1);
  }
 
  return (
    <button onClick = {handleClick}>Like ({likes})</button>
  );
}

////////// EXPORTS //////////
export { LikeButton };
app/layout.js
export const metadata = {
  title: 'Next.js',
  description: 'Generated by Next.js',
}

export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  )
}
## Comparing the Approaches ### Vanilla JS (Imperative) The imperative approach directly manipulates the DOM using procedural code. It's straightforward but can become difficult to maintain as applications grow. Key characteristics: - Direct DOM manipulation - Mutable state that's modified in-place - Event handlers that directly update the UI - Limited separation between state and UI logic - One-time rendering with targeted updates This approach is closest to how browsers natively work, making it easy to understand for beginners, but it doesn't scale well to complex applications. ### Vanilla JS (Functional) The functional approach applies principles of functional programming to UI development. It emphasizes: - Pure functions with minimal side effects - Immutable state (creating new state objects rather than modifying existing ones) - Composition of smaller functions into larger UI structures - Explicit re-rendering of the entire UI when state changes - Clear separation between state management and UI rendering This approach is more maintainable than the imperative version but still requires manual state management and re-rendering. ### React React introduces a component-based architecture with declarative UI rendering: - Components that encapsulate both UI and behavior - Declarative syntax using JSX - Automatic UI updates when state changes - Built-in state management with hooks - Virtual DOM for efficient updates React abstracts away the complexities of DOM manipulation and state management, allowing developers to focus on the application logic. ### Next.js Next.js builds on React with additional features: - Server components for improved performance - Clear separation between client and server code - File-based routing - Built-in optimizations for production - Component-based architecture with clear separation of concerns Next.js represents the modern approach to web development, combining the benefits of React with additional features for production applications. ## Conclusion Each approach has its strengths and appropriate use cases: - **Vanilla JS (Imperative)**: Good for small projects or when you need to understand the fundamentals - **Vanilla JS (Functional)**: Better for medium-sized projects with a focus on maintainability - **React**: Excellent for complex, interactive applications - **Next.js**: Ideal for production applications that need performance and SEO The evolution from imperative to reactive paradigms reflects the increasing complexity of web applications and the need for more maintainable, scalable approaches to UI development. Understanding these different paradigms helps you choose the right tool for each project and appreciate the trade-offs involved in modern web development.