DEV INDUSTRY Kaj Białas

React Hooks Revolution

Back to articles
React

React Hooks Revolution

React has undergone a remarkable transformation since its introduction in 2013. As someone who has worked with React through its major evolution phases, I've witnessed firsthand how the introduction of Hooks in React 16.8 fundamentally changed how we build user interfaces. This article explores the practical implications of this evolution and provides insights for modern React development.

The Legacy: Class Components Era

Before Hooks, React development was characterized by a clear distinction between stateful class components and stateless functional components. This separation created predictable patterns but also introduced complexity.

Classic Class Component Architecture

1import React, { Component } from 'react';
2
3class UserProfile extends Component {
4  constructor(props) {
5    super(props);
6    this.state = {
7      user: null,
8      loading: true,
9      error: null
10    };
11  }
12
13  async componentDidMount() {
14    try {
15      const response = await fetch(`/api/users/${this.props.userId}`);
16      const user = await response.json();
17      this.setState({ user, loading: false });
18    } catch (error) {
19      this.setState({ error: error.message, loading: false });
20    }
21  }
22
23  componentDidUpdate(prevProps) {
24    if (prevProps.userId !== this.props.userId) {
25      this.setState({ loading: true });
26      this.fetchUser();
27    }
28  }
29
30  componentWillUnmount() {
31    // Cleanup subscriptions, cancel requests, etc.
32    this.abortController?.abort();
33  }
34
35  render() {
36    const { user, loading, error } = this.state;
37    
38    if (loading) return <div>Loading...</div>;
39    if (error) return <div>Error: {error}</div>;
40    
41    return (
42      <div className="user-profile">
43        <h1>{user.name}</h1>
44        <p>{user.email}</p>
45      </div>
46    );
47  }
48}
49
50export default UserProfile;

The Container/Presentational Pattern

This era popularized the separation of concerns through Container and Presentational components:

1// Container Component - handles logic and state
2class UserProfileContainer extends Component {
3  state = {
4    user: null,
5    loading: true
6  };
7
8  componentDidMount() {
9    this.fetchUser();
10  }
11
12  fetchUser = async () => {
13    const user = await userService.getUser(this.props.userId);
14    this.setState({ user, loading: false });
15  };
16
17  render() {
18    return (
19      <UserProfilePresentation 
20        user={this.state.user}
21        loading={this.state.loading}
22        onRefresh={this.fetchUser}
23      />
24    );
25  }
26}
27
28// Presentational Component - pure rendering
29const UserProfilePresentation = ({ user, loading, onRefresh }) => {
30  if (loading) return <Spinner />;
31  
32  return (
33    <div className="user-profile">
34      <h1>{user.name}</h1>
35      <button onClick={onRefresh}>Refresh</button>
36    </div>
37  );
38};

The Revolution: Hooks Era

The introduction of Hooks transformed React development by enabling functional components to manage state and side effects, eliminating the need for class components in most scenarios.

Modern Functional Component

1import React, { useState, useEffect } from 'react';
2
3const UserProfile = ({ userId }) => {
4  const [user, setUser] = useState(null);
5  const [loading, setLoading] = useState(true);
6  const [error, setError] = useState(null);
7
8  useEffect(() => {
9    const abortController = new AbortController();
10    
11    const fetchUser = async () => {
12      try {
13        setLoading(true);
14        const response = await fetch(`/api/users/${userId}`, {
15          signal: abortController.signal
16        });
17        const userData = await response.json();
18        setUser(userData);
19        setError(null);
20      } catch (err) {
21        if (err.name !== 'AbortError') {
22          setError(err.message);
23        }
24      } finally {
25        setLoading(false);
26      }
27    };
28
29    fetchUser();
30
31    return () => {
32      abortController.abort();
33    };
34  }, [userId]);
35
36  if (loading) return <div>Loading...</div>;
37  if (error) return <div>Error: {error}</div>;
38
39  return (
40    <div className="user-profile">
41      <h1>{user.name}</h1>
42      <p>{user.email}</p>
43    </div>
44  );
45};
46
47export default UserProfile;

Custom Hooks: Reusable Logic Revolution

One of Hooks' most powerful features is the ability to extract component logic into reusable custom hooks.

Creating a Custom Data Fetching Hook

1import { useState, useEffect, useCallback } from 'react';
2
3const useApi = (url, dependencies = []) => {
4  const [data, setData] = useState(null);
5  const [loading, setLoading] = useState(true);
6  const [error, setError] = useState(null);
7
8  const fetchData = useCallback(async () => {
9    try {
10      setLoading(true);
11      setError(null);
12      
13      const response = await fetch(url);
14      if (!response.ok) {
15        throw new Error(`HTTP error! status: ${response.status}`);
16      }
17      
18      const result = await response.json();
19      setData(result);
20    } catch (err) {
21      setError(err.message);
22    } finally {
23      setLoading(false);
24    }
25  }, [url]);
26
27  useEffect(() => {
28    fetchData();
29  }, [fetchData, ...dependencies]);
30
31  const refetch = useCallback(() => {
32    fetchData();
33  }, [fetchData]);
34
35  return { data, loading, error, refetch };
36};
37
38// Usage in component
39const UserProfile = ({ userId }) => {
40  const { data: user, loading, error, refetch } = useApi(
41    `/api/users/${userId}`,
42    [userId]
43  );
44
45  if (loading) return <div>Loading...</div>;
46  if (error) return <div>Error: {error}</div>;
47
48  return (
49    <div className="user-profile">
50      <h1>{user.name}</h1>
51      <p>{user.email}</p>
52      <button onClick={refetch}>Refresh</button>
53    </div>
54  );
55};

Performance Optimization with Hooks

Modern React provides powerful optimization tools that were complex or impossible with class components.

useMemo and useCallback

1import React, { useState, useMemo, useCallback } from 'react';
2
3const ExpensiveComponent = ({ items, filter, onItemClick }) => {
4  // Memoize expensive calculations
5  const filteredItems = useMemo(() => {
6    return items.filter(item => 
7      item.name.toLowerCase().includes(filter.toLowerCase())
8    ).sort((a, b) => a.priority - b.priority);
9  }, [items, filter]);
10
11  // Memoize event handlers
12  const handleItemClick = useCallback((itemId) => {
13    onItemClick(itemId);
14  }, [onItemClick]);
15
16  const expensiveValue = useMemo(() => {
17    return filteredItems.reduce((acc, item) => acc + item.value, 0);
18  }, [filteredItems]);
19
20  return (
21    <div>
22      <div>Total Value: {expensiveValue}</div>
23      {filteredItems.map(item => (
24        <ItemComponent
25          key={item.id}
26          item={item}
27          onClick={handleItemClick}
28        />
29      ))}
30    </div>
31  );
32};
33
34const ItemComponent = React.memo(({ item, onClick }) => {
35  return (
36    <div 
37      className="item"
38      onClick={() => onClick(item.id)}
39    >
40      <h3>{item.name}</h3>
41      <p>{item.description}</p>
42    </div>
43  );
44});

Context API Evolution

The combination of Context API and Hooks provides a powerful alternative to external state management libraries.

Modern Context Pattern

1import React, { createContext, useContext, useReducer, useMemo } from 'react';
2
3const AuthContext = createContext();
4
5const authReducer = (state, action) => {
6  switch (action.type) {
7    case 'LOGIN_START':
8      return { ...state, loading: true, error: null };
9    
10    case 'LOGIN_SUCCESS':
11      return { 
12        ...state, 
13        loading: false, 
14        user: action.payload, 
15        isAuthenticated: true 
16      };
17    
18    case 'LOGIN_ERROR':
19      return { 
20        ...state, 
21        loading: false, 
22        error: action.payload, 
23        isAuthenticated: false 
24      };
25    
26    case 'LOGOUT':
27      return { 
28        ...state, 
29        user: null, 
30        isAuthenticated: false 
31      };
32    
33    default:
34      return state;
35  }
36};
37
38export const AuthProvider = ({ children }) => {
39  const [state, dispatch] = useReducer(authReducer, {
40    user: null,
41    isAuthenticated: false,
42    loading: false,
43    error: null
44  });
45
46  const login = async (credentials) => {
47    dispatch({ type: 'LOGIN_START' });
48    try {
49      const response = await authService.login(credentials);
50      dispatch({ type: 'LOGIN_SUCCESS', payload: response.user });
51    } catch (error) {
52      dispatch({ type: 'LOGIN_ERROR', payload: error.message });
53    }
54  };
55
56  const logout = () => {
57    authService.logout();
58    dispatch({ type: 'LOGOUT' });
59  };
60
61  const value = useMemo(() => ({
62    ...state,
63    login,
64    logout
65  }), [state]);
66
67  return (
68    <AuthContext.Provider value={value}>
69      {children}
70    </AuthContext.Provider>
71  );
72};
73
74export const useAuth = () => {
75  const context = useContext(AuthContext);
76  if (!context) {
77    throw new Error('useAuth must be used within an AuthProvider');
78  }
79  return context;
80};

Key Takeaways

The evolution from class components to Hooks represents more than a syntactic change—it's a fundamental shift in how we think about React application architecture:

  • Simplified mental model: Hooks provide a more direct way to work with React features
  • Better code reuse: Custom hooks enable logic sharing without component wrapper complexity
  • Improved performance: Both in terms of bundle size and runtime characteristics
  • Enhanced developer experience: Easier testing, debugging, and code organization

For teams still using class components, I recommend a gradual migration approach, starting with new components and progressively updating existing ones when modifications are needed.

About me

About me

Tech Leader and Front-end Developer with 10 years of experience, placing a particular emphasis on application architecture, understanding of business domains, and the testability of developed solutions.

As a trainer and course author, I attach great importance to the quality aspects of projects, as well as to enhancing team competence and awareness.

I am an enthusiast of the Domain Driven Design approach, a DevOps practitioner, and a dedicated fan of the React ecosystem.

In my free time, I relax to the sounds of hard rock and enjoy a glass of good whisky.

01 Front-end Development
02 Solution Architecture
03 Unit Testing
04 Dev Ops
05 React
Zdjęcie Kaj Białas - Frontend Tech Lead