Why React’s Hooks Sparked a New UI Paradigm—and What Comes Next
The article traces React’s evolution from early JSX‑based componentization to the Hooks‑driven functional component era, compares alternative approaches like Solid.js, Vue, and Svelte, and reflects on the underlying principles of separation of concerns and intimacy that drive UI paradigm shifts.
Preface
The author originally planned to criticize React Hooks but ended up discussing the broader evolution of UI paradigms, noting that React has driven two major frontend revolutions.
Historical Background
In 2013, when React was released, most teams still wrote HTML, JavaScript, and CSS in separate files. JSX, which mixes HTML and JS, felt like a violation of the traditional separation of concerns principle.
Early front‑end development relied on jQuery to manipulate a large, complex DOM tree, leading to maintenance nightmares as code spread across thousands of lines.
First Componentization Revolution
Background: Growing page complexity hindered productivity.
Idea: Componentization and data‑driven rendering.
Tool: JSX syntax.
Impact: Introduced a compilation era for front‑end projects.
Result: Component‑based architecture became universal.
Data‑driven views replaced direct DOM manipulation.
React split pages into components, merging structure (HTML) and behavior (JS) while letting data drive the view. The component’s state lives inside the function, and the render output is expressed directly in JSX.
<ul>{list.map((it) => (</code><code> <li>{it}</li>))}</code><code></ul>2013 – 2019: Class Component Era
Class components grew large: a single state object, a massive render method, and many lifecycle methods scattered across the file.
this.state : huge state object.
this.render : massive JSX tree.
other methods : handle user interactions and mutate state.
class Component {</code><code> constructor() {</code><code> this.state = { count: 0, /* other state */ };</code><code> }</code><code> handleClick = (evt) => {</code><code> this.setState({ count: this.state.count + 1 });</code><code> };</code><code> render() {</code><code> return (</code><code> <div></code><code> {/* other nodes */}</code><code> <button onClick={this.handleClick}>Clicked {this.state.count} times</button></code><code> {/* other nodes */}</code><code> </div></code><code> );</code><code> }</code><code>}While code reviews and conventions can tame the chaos, class components are verbose and encourage monolithic components, contrary to React’s original intent.
Second Function Component Revolution (Hooks)
Background: Class components became unwieldy.
Idea: Functional components with UI = f(state) .
Tool: Hooks API.
Impact: Hooks made stateful functional components the new norm.
Hooks fit naturally into JavaScript syntax, allowing functions to hold local state without the boilerplate of classes.
function Counter() {</code><code> const [count, setCount] = useState(0);</code><code> function handleClick() { setCount(count + 1); }</code><code> return <button onClick={handleClick}>Clicked {count} times</button>;</code><code>}Hooks are praised for their simplicity, but their “run‑the‑function‑again” model introduces drawbacks:
Developers must manually manage useEffect dependencies.
Performance often requires useMemo or useCallback as patches.
Closures can capture stale state. setState is asynchronous, adding mental overhead.
import React, { useState, useEffect, useCallback } from 'react';</code><code>function ExampleComponent({ fetchData }) {</code><code> const [data, setData] = useState(null);</code><code> const [count, setCount] = useState(0);</code><code> // Manual dependency management</code><code> useEffect(() => {</code><code> fetchData().then(r => setData(r));</code><code> }, [a, b, c]);</code><code> const handleAlertClick = () => {</code><code> setTimeout(() => alert(`Count: ${count}`), 3000);</code><code> };</code><code> const increment = useCallback(() => setCount(c => c + 1), []);</code><code> const handleMultipleIncrements = () => {</code><code> setCount(count + 1);</code><code> setCount(count + 1); // only increments once because setState is async</code><code> };</code><code> return (</code><code> <div></code><code> <p>Data: {data}</p></code><code> <p>Count: {count}</p></code><code> <button onClick={increment}>Increment</button></code><code> <button onClick={handleAlertClick}>Show Alert</button></code><code> <button onClick={handleMultipleIncrements}>Increment Twice</button></code><code> </div></code><code> );</code><code>}Improving Hooks: Solid.js, Vue, Svelte
Solid.js replaces useState with createSignal, automatically tracking dependencies and eliminating the need for useEffect and memoization.
function Counter() {</code><code> const [count, setCount] = createSignal(0);</code><code> createEffect(() => console.log('The count is now', count()));</code><code> return <button onClick={() => setCount(count() + 1)}>Click Me</button>;</code><code>}Vue’s Composition API mirrors Hooks but uses ref and reactive proxies, removing the explicit setState function.
const count = ref(1);</code><code>const obj = reactive({ count });</code><code>count.value++; // updates obj.count automaticallySvelte goes further: a variable marked with $state becomes reactive, and the compiler injects change tracking, eliminating runtime overhead.
<script></code><code> let count = $state(0);</code><code> function handleClick() { count += 1; }</code><code></script></code><code><button onclick="{handleClick}">Clicked {count} times</button>Current Ideal UI Paradigm
The author speculates that if React had adopted a signal‑like API from the start, developers would enjoy a cleaner experience. The ideal is a state variable that is a plain value, no extra wrappers, and a rendering engine that updates only what changed.
Future Trends: Front‑End / Back‑End Fusion
Modern frameworks like Next.js allow server‑side functions to be imported directly into client components, blurring the line between front‑end and back‑end code. This “full‑stack UI” approach can reduce boilerplate but ties UI logic tightly to specific business domains.
// src/api.server.js</code><code>export async function getData(id) { return db.query(id); }</code><code>// src/ui.client.jsx</code><code>'use client';</code><code>import { getData } from './action.server';</code><code>function UIComponent() {</code><code> const [data, setData] = useState('');</code><code> useEffect(() => { (async () => { setData(await getData(42)); })(); }, []);</code><code> return <span>{data}</span>;</code><code>}Whether this full‑stack UI model will be widely adopted depends on the collaboration dynamics between front‑end and back‑end teams.
Reflection
The article concludes that paradigm shifts are driven by the “principle of intimacy”: code that works toward a common goal should be physically close, and growing complexity forces us to reorganize code to preserve that intimacy.
Key takeaways:
Mark variables as component state (e.g., $state or signals).
Avoid repeated component function execution by using reactive systems like Svelte’s compiler or Solid’s signals.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Bilibili Tech
Provides introductions and tutorials on Bilibili-related technologies.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
