The Evolution of Managing “State" in Web Development

Slug
evolution-of-hooks
Description
A simple journey through the different ways we have managed “state” throughout the history of web development.
Date
Sep 15, 2024
Published
Published

Who is this article for?

This article is for everyone—whether you're new to web development or have some experience. I’m going to do my best to break down the ideas into (hopefully) easier-to-understand concepts.
 
We'll explore what React’s “hooks” are, why they matter, and how it and other state management solutions have evolved over time to make creating dynamic websites more intuitive, scalable, and arguably, better.
 
💡
I had to research much of the pre-1995, pre-JavaScript history within this article. It was a very interesting dive into the past to see how things have evolved. If you spot any inaccuracies please feel free to let me know! Email: jdobson3@gmail.com
 

Introduction

Have you ever wondered how websites can remember things like your shopping cart items or how they update without refreshing the page?
 
Well, you've come to the right place! In this article, we'll dive into React’s hooks feature, a key tool of one of the most popular modern frontend frameworks which provide developers with powerful tools to build responsive, dynamic webapps.
 
We'll take a journey back in time to see how developers used to handle these challenges before hooks existed.
 
My thought process is that by looking into their evolution over the decades, we can not only build a better understanding, but also some new insights.
 
Here's what we'll cover:
  1. Understanding the Basics: What are Hooks?
  1. The Early Days: How We Handled State and Updates Before
  1. The Rise of JavaScript Frameworks: Class Components and Stateful Logic
  1. The Arrival of Hooks: A Game Changer
  1. Hooks Today: How We Use Them
  1. Simple Examples: Hooks in Action
 
Let's jump in!

Understanding the Basics: What are Hooks?

What is a Hook?

Imagine a React hook as like a special tool or feature that make building websites easier. They let you "hook into" or tap into special features without doing lots of complicated work.
 
In web development, especially with frameworks like React, hooks let you use features like remembering data (state) and responding to changes (side effects) in your web components without needing complex setups.
 
⚠️
In other frameworks, similar concepts to React's hooks may be implemented, but they go by different names or patterns:
  1. Vue.js (Composition API):
      • Vue 3 introduced the Composition API, which is conceptually similar to hooks in React. It allows you to organize and reuse reactive state and logic in a more flexible way within Vue components.
      • Example: ref, reactive, watch, onMounted.
  1. Svelte (Stores and Lifecycle Functions):
      • In Svelte, "stores" manage shared state and reactive values, similar to useState in React.
      • Svelte also provides lifecycle functions (e.g., onMount, beforeUpdate, afterUpdate) for side effects, like useEffect in React.
  1. Angular (Services and Lifecycle Hooks):
      • Angular relies on services for managing state and logic across components, and "lifecycle hooks" such as ngOnInit and ngAfterViewInit for handling side effects and component lifecycles.
 
While these frameworks’ features fulfill very similar roles to React hooks, each framework has their own flavors and implementations of a centralized state management solution.
 

The Early Days: How We Handled State and Updates Before

 

Static Web Pages

In the very beginning, websites were mostly static. They were like digital brochures—every time you wanted to see something new, the whole page had to reload.
Characteristics:
  • No Interactivity: Pages didn't change unless you navigated to a new one.
  • Server-Centric: All the work was done on the server, and the browser just displayed the result.
 

Introducing JavaScript

In 1995, JavaScript was introduced to make web pages interactive.
What JavaScript Brought:
  • Dynamic Content: Change parts of the page without reloading.
  • Event Handling: Respond to user actions like clicks and key presses.
 

Managing State with Vanilla JavaScript

Before libraries and frameworks, developers used plain JavaScript (often called "vanilla JavaScript") to manage state and updates.
Example:
 
<!DOCTYPE html> <html> <head> <title>Counter Example</title> <script> let count = 0; function increment() { count++; document.getElementById('count').innerText = count; } </script> </head> <body> <p>You clicked <span id="count">0</span> times</p> <button onclick="increment()">Click me</button> </body> </html>
 
Challenges:
  • Global Variables: Managing state with global variables could lead to conflicts.
  • Manual DOM Manipulation: Updating the page required directly changing the Document Object Model (DOM), which could get messy.
  • Complexity Grows Quickly: As applications became more complex, managing state and updates became harder.

 

The Rise of JavaScript Frameworks: Class Components and Stateful Logic

 

Enter jQuery and DOM Manipulation

In 2006, jQuery became popular, simplifying DOM manipulation and event handling.
What jQuery Offered:
  • Simplified Syntax: Easier to select and manipulate elements.
  • Cross-Browser Compatibility: Handled differences between browsers.
  • Chainable Methods: Write cleaner code.
Example with jQuery:
 
<!DOCTYPE html> <html> <head> <title>Counter Example</title> <script src="<https://code.jquery.com/jquery-3.6.0.min.js>"></script> <script> $(function() { let count = 0; $('#button').click(function() { count++; $('#count').text(count); }); }); </script> </head> <body> <p>You clicked <span id="count">0</span> times</p> <button id="button">Click me</button> </body> </html>
 
Limitations:
  • Still Uses Global State: Managing state across different parts of the app was still tricky.
  • Spaghetti Code: Code could become tangled and hard to maintain as the app grew.
 

Single Page Applications (SPAs) and MVC Frameworks

As web apps became more interactive, developers needed better ways to manage state and updates.
Introducing Frameworks:
  • Backbone.js (2010): Provided models, views, and collections to structure apps.
  • AngularJS (2010): Introduced two-way data binding and dependency injection.
  • Ember.js (2011): Focused on convention over configuration.
Benefits:
  • Organized Code Structure: Separation of concerns made code easier to manage.
  • Data Binding: Automatically updated the view when the model changed.
But Still Challenges:
  • Steep Learning Curve: Complex frameworks required significant learning.
  • Performance Issues: Two-way data binding could be inefficient.
 

React and Class Components

In 2013, React was introduced by Facebook, changing the game with a new approach.
Key Concepts:
  • Virtual DOM: Efficiently updates the DOM by only changing what's necessary.
  • Component-Based Architecture: Build UI using reusable components.
  • One-Way Data Flow: Simplifies data management.
Using Class Components for State:
 
class Counter extends React.Component { constructor(props) { super(props); this.state = { count: 0 }; } increment = () => { this.setState({ count: this.state.count + 1 }); }; render() { return ( <div> <p>You clicked {this.state.count} times</p> <button onClick={this.increment}> Click me </button> </div> ); } }
 
Challenges with Class Components:
  • Complex Syntax: Classes and binding methods added complexity.
  • Stateful Logic Sharing: Difficult to share stateful logic between components.
  • Understanding this: Managing the this keyword could be confusing.

The Arrival of Hooks: A Game Changer

 

How Hooks Changed Everything

In 2019, React introduced hooks, providing a simpler way to handle state and side effects without classes.
What Hooks Brought:
  • Functional Components with State: Use functions instead of classes while still having state.
  • Simplified Code: Less boilerplate and easier to read.
  • Better Logic Sharing: Custom hooks allow for reusable stateful logic.
The Same Counter with Hooks:
 
import React, { useState } from 'react'; function Counter() { const [count, setCount] = useState(0); const increment = () => { setCount(count + 1); }; return ( <div> <p>You clicked {count} times</p> <button onClick={increment}> Click me </button> </div> ); }
 
Advantages:
  • No More Classes: Simplifies components to plain functions.
  • Easier to Understand: Focus on what the component does, not how it's built.
  • Flexible Logic: Extract and reuse logic with custom hooks.

 
Hooks Today: How We Use Them
 

Common Hooks You Should Know

  • useState: Allows you to add state to your components.
  • useEffect: Lets you perform actions like fetching data when your component loads or updates.
  • useContext: Helps you use shared data without passing props down manually.
  • Custom Hooks: You can make your own hooks to reuse code across components.

Hooks in Other Frameworks

The success of hooks in React influenced other frameworks:
  • Vue.js Composition API: Similar to hooks, it allows for better logic reuse.
  • Svelte: Uses reactive statements and stores for state management.
  • Angular: Introduced concepts like observables and reactive programming.

Simple Examples: Hooks in Action

 

Using useState

Suppose you want to keep track of a number that changes when you click a button.
 
import React, { useState } from 'react'; function NumberTracker() { const [number, setNumber] = useState(0); const increase = () => { setNumber(number + 1); }; return ( <div> <p>The number is {number}</p> <button onClick={increase}> Increase </button> </div> ); }
 
What's Happening Here?
  • useState(0) initializes the state variable number to 0.
  • setNumber updates the state, and the component re-renders.

Using useEffect

Suppose you want to fetch data when your component loads.
 
import React, { useState, useEffect } from 'react'; function DataFetcher() { const [data, setData] = useState(null); useEffect(() => { fetch('/some-api') .then(response => response.json()) .then(data => setData(data)); }, []); if (!data) return <p>Loading...</p>; return <div>Data: {JSON.stringify(data)}</div>; }
 
Understanding useEffect:
  • Runs after the component renders.
  • The empty array [] ensures it only runs once.
  • Fetches data and updates state with setData.
 

Creating a Custom Hook

Let's create a hook that tracks the mouse position.
 
import { useState, useEffect } from 'react'; function useMousePosition() { const [position, setPosition] = useState({ x: null, y: null }); useEffect(() => { function handleMouseMove(event) { setPosition({ x: event.clientX, y: event.clientY, }); } window.addEventListener('mousemove', handleMouseMove); return () => { window.removeEventListener('mousemove', handleMouseMove); }; }, []); return position; } // Using our custom hook function MouseTracker() { const position = useMousePosition(); return ( <div> <p>Mouse position: {position.x}, {position.y}</p> </div> ); }
 
Why Use a Custom Hook?
  • Reusability: Use useMousePosition in any component.
  • Clean Separation: Keeps your component code focused.

Conclusion

From the early days of static pages to the dynamic web apps we have today, managing state and updates has always been a challenge. Hooks have transformed the way we build websites by making it easier to handle these tasks.
 
Final Takeaway: Hooks are like handy tools that simplify web development. They let us write cleaner code, reuse logic, and make our websites more interactive without the headaches of older methods.
 
💡
As a lil anecdotal side-point that I’ve learned, especially in tech, is that just because something is newer does not mean it is inherently better. It’s okay to be scrutinous of new technologies, weighing their pros/cons against existing methods. This will result in a better analysis of new techs, what they do differently, what competitive advantages they hold over existing methods, and what specific use cases do they excel in.
 

Thanks for reading!

 

Contact Me!