Skip to content

React State

Overview

State is data in React components that can change over time. State enables components to respond to user interactions, network requests, or other events. This chapter will learn how to use the useState Hook to manage state in function components.

🔄 What is State

State vs Props

jsx
// Props are passed from outside, read-only
function Greeting({ name }) {
    return <h1>Hello, {name}!</h1>;
}

// State is internal to the component, mutable
function Counter() {
    const [count, setCount] = useState(0);

    return (
        <div>
            <p>Current count: {count}</p>
            <button onClick={() => setCount(count + 1)}>
                Increment
            </button>
        </div>
    );
}

State Characteristics

  • Locality: Each component instance has its own state
  • Asynchronous Updates: State updates may be asynchronous
  • Triggers Re-render: State changes cause components to re-render

🎣 useState Hook

Basic Usage

jsx
import { useState } from 'react';

function SimpleCounter() {
    // [state value, update function] = useState(initial value)
    const [count, setCount] = useState(0);

    const increment = () => setCount(count + 1);
    const decrement = () => setCount(count - 1);
    const reset = () => setCount(0);

    return (
        <div>
            <h2>Counter: {count}</h2>
            <button onClick={increment}>+1</button>
            <button onClick={decrement}>-1</button>
            <button onClick={reset}>Reset</button>
        </div>
    );
}

Different Types of State

jsx
function StateDemoApp() {
    // Number state
    const [age, setAge] = useState(25);

    // String state
    const [name, setName] = useState('');

    // Boolean state
    const [isVisible, setIsVisible] = useState(true);

    // Array state
    const [items, setItems] = useState(['Apple', 'Banana']);

    // Object state
    const [user, setUser] = useState({
        name: 'John',
        email: 'john@example.com'
    });

    const addItem = () => {
        setItems([...items, `New Item ${items.length + 1}`]);
    };

    const updateUser = () => {
        setUser({
            ...user,
            name: 'Jane'
        });
    };

    return (
        <div>
            <h3>Name: {name}</h3>
            <input
                value={name}
                onChange={(e) => setName(e.target.value)}
                placeholder="Enter name"
            />

            <h3>Age: {age}</h3>
            <button onClick={() => setAge(age + 1)}>Increase Age</button>

            <button onClick={() => setIsVisible(!isVisible)}>
                {isVisible ? 'Hide' : 'Show'} Content
            </button>

            {isVisible && (
                <div>
                    <h3>Item List:</h3>
                    <ul>
                        {items.map((item, index) => (
                            <li key={index}>{item}</li>
                        ))}
                    </ul>
                    <button onClick={addItem}>Add Item</button>
                </div>
            )}

            <h3>User: {user.name} ({user.email})</h3>
            <button onClick={updateUser}>Update User</button>
        </div>
    );
}

🔄 State Update Patterns

Functional Updates

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

    const increment = () => {
        // Update based on previous state value
        setCount(prevCount => prevCount + 1);
    };

    const incrementByFive = () => {
        // Use functional updates for consecutive updates
        setCount(prev => prev + 1);
        setCount(prev => prev + 1);
        setCount(prev => prev + 1);
        setCount(prev => prev + 1);
        setCount(prev => prev + 1);
    };

    return (
        <div>
            <p>Count: {count}</p>
            <button onClick={increment}>+1</button>
            <button onClick={incrementByFive}>+5</button>
        </div>
    );
}

Object State Updates

jsx
function UserProfile() {
    const [user, setUser] = useState({
        name: '',
        email: '',
        age: 0,
        preferences: {
            theme: 'light',
            language: 'en'
        }
    });

    const updateName = (name) => {
        setUser(prevUser => ({
            ...prevUser,
            name
        }));
    };

    const updateEmail = (email) => {
        setUser(prevUser => ({
            ...prevUser,
            email
        }));
    };

    const updatePreferences = (newPrefs) => {
        setUser(prevUser => ({
            ...prevUser,
            preferences: {
                ...prevUser.preferences,
                ...newPrefs
            }
        }));
    };

    return (
        <div>
            <input
                placeholder="Name"
                value={user.name}
                onChange={(e) => updateName(e.target.value)}
            />

            <input
                placeholder="Email"
                value={user.email}
                onChange={(e) => updateEmail(e.target.value)}
            />

            <select
                value={user.preferences.theme}
                onChange={(e) => updatePreferences({ theme: e.target.value })}
            >
                <option value="light">Light Theme</option>
                <option value="dark">Dark Theme</option>
            </select>

            <div>
                <h3>User Info:</h3>
                <p>Name: {user.name}</p>
                <p>Email: {user.email}</p>
                <p>Theme: {user.preferences.theme}</p>
            </div>
        </div>
    );
}

Array State Updates

jsx
function TodoList() {
    const [todos, setTodos] = useState([
        { id: 1, text: 'Learn React', completed: false },
        { id: 2, text: 'Write Code', completed: true }
    ]);
    const [newTodo, setNewTodo] = useState('');

    const addTodo = () => {
        if (newTodo.trim()) {
            setTodos(prevTodos => [
                ...prevTodos,
                {
                    id: Date.now(),
                    text: newTodo,
                    completed: false
                }
            ]);
            setNewTodo('');
        }
    };

    const toggleTodo = (id) => {
        setTodos(prevTodos =>
            prevTodos.map(todo =>
                todo.id === id
                    ? { ...todo, completed: !todo.completed }
                    : todo
            )
        );
    };

    const deleteTodo = (id) => {
        setTodos(prevTodos =>
            prevTodos.filter(todo => todo.id !== id)
        );
    };

    return (
        <div>
            <div>
                <input
                    value={newTodo}
                    onChange={(e) => setNewTodo(e.target.value)}
                    placeholder="Add new task"
                />
                <button onClick={addTodo}>Add</button>
            </div>

            <ul>
                {todos.map(todo => (
                    <li key={todo.id}>
                        <input
                            type="checkbox"
                            checked={todo.completed}
                            onChange={() => toggleTodo(todo.id)}
                        />
                        <span style={{
                            textDecoration: todo.completed ? 'line-through' : 'none'
                        }}>
                            {todo.text}
                        </span>
                        <button onClick={() => deleteTodo(todo.id)}>Delete</button>
                    </li>
                ))}
            </ul>
        </div>
    );
}

🎛️ Complex State Management

Using useReducer

jsx
import { useReducer } from 'react';

// Define reducer function
function counterReducer(state, action) {
    switch (action.type) {
        case 'increment':
            return { count: state.count + 1 };
        case 'decrement':
            return { count: state.count - 1 };
        case 'reset':
            return { count: 0 };
        case 'set':
            return { count: action.payload };
        default:
            throw new Error(`Unknown action type: ${action.type}`);
    }
}

function AdvancedCounter() {
    const [state, dispatch] = useReducer(counterReducer, { count: 0 });

    return (
        <div>
            <p>Count: {state.count}</p>
            <button onClick={() => dispatch({ type: 'increment' })}>
                +1
            </button>
            <button onClick={() => dispatch({ type: 'decrement' })}>
                -1
            </button>
            <button onClick={() => dispatch({ type: 'reset' })}>
                Reset
            </button>
            <button onClick={() => dispatch({ type: 'set', payload: 100 })}>
                Set to 100
            </button>
        </div>
    );
}

Custom Hook for State Logic Encapsulation

jsx
// Custom Hook: Form state management
function useForm(initialValues) {
    const [values, setValues] = useState(initialValues);
    const [errors, setErrors] = useState({});

    const handleChange = (name, value) => {
        setValues(prev => ({
            ...prev,
            [name]: value
        }));

        // Clear errors
        if (errors[name]) {
            setErrors(prev => ({
                ...prev,
                [name]: ''
            }));
        }
    };

    const handleSubmit = (onSubmit, validationRules = {}) => {
        return (e) => {
            e.preventDefault();

            const newErrors = {};
            Object.keys(validationRules).forEach(field => {
                const rule = validationRules[field];
                if (rule.required && !values[field]) {
                    newErrors[field] = `${rule.label || field} is required`;
                }
            });

            setErrors(newErrors);

            if (Object.keys(newErrors).length === 0) {
                onSubmit(values);
            }
        };
    };

    const reset = () => {
        setValues(initialValues);
        setErrors({});
    };

    return {
        values,
        errors,
        handleChange,
        handleSubmit,
        reset
    };
}

// Using custom Hook
function ContactForm() {
    const { values, errors, handleChange, handleSubmit, reset } = useForm({
        name: '',
        email: '',
        message: ''
    });

    const onSubmit = (formData) => {
        console.log('Form submitted:', formData);
        alert('Form submitted successfully!');
        reset();
    };

    const validationRules = {
        name: { required: true, label: 'Name' },
        email: { required: true, label: 'Email' },
        message: { required: true, label: 'Message' }
    };

    return (
        <form onSubmit={handleSubmit(onSubmit, validationRules)}>
            <div>
                <input
                    placeholder="Name"
                    value={values.name}
                    onChange={(e) => handleChange('name', e.target.value)}
                />
                {errors.name && <span className="error">{errors.name}</span>}
            </div>

            <div>
                <input
                    type="email"
                    placeholder="Email"
                    value={values.email}
                    onChange={(e) => handleChange('email', e.target.value)}
                />
                {errors.email && <span className="error">{errors.email}</span>}
            </div>

            <div>
                <textarea
                    placeholder="Message"
                    value={values.message}
                    onChange={(e) => handleChange('message', e.target.value)}
                />
                {errors.message && <span className="error">{errors.message}</span>}
            </div>

            <button type="submit">Submit</button>
            <button type="button" onClick={reset}>Reset</button>
        </form>
    );
}

⚡ State Performance Optimization

State Splitting

jsx
// ❌ All state in one object
function BadComponent() {
    const [state, setState] = useState({
        user: null,
        posts: [],
        loading: false,
        theme: 'light',
        sidebar: false
    });

    // Any field update causes the entire component to re-render
}

// ✅ Separate unrelated state
function GoodComponent() {
    const [user, setUser] = useState(null);
    const [posts, setPosts] = useState([]);
    const [loading, setLoading] = useState(false);
    const [theme, setTheme] = useState('light');
    const [sidebar, setSidebar] = useState(false);

    // Each state updates independently, reducing unnecessary re-renders
}

Using useMemo and useCallback for Optimization

jsx
import { useState, useMemo, useCallback } from 'react';

function OptimizedList({ items }) {
    const [filter, setFilter] = useState('');
    const [sortBy, setSortBy] = useState('name');

    // Cache filtered and sorted results
    const filteredAndSortedItems = useMemo(() => {
        const filtered = items.filter(item =>
            item.name.toLowerCase().includes(filter.toLowerCase())
        );

        return filtered.sort((a, b) => {
            if (sortBy === 'name') {
                return a.name.localeCompare(b.name);
            }
            return a[sortBy] - b[sortBy];
        });
    }, [items, filter, sortBy]);

    // Cache event handlers
    const handleFilterChange = useCallback((e) => {
        setFilter(e.target.value);
    }, []);

    const handleSortChange = useCallback((e) => {
        setSortBy(e.target.value);
    }, []);

    return (
        <div>
            <input
                placeholder="Search..."
                value={filter}
                onChange={handleFilterChange}
            />

            <select value={sortBy} onChange={handleSortChange}>
                <option value="name">Sort by Name</option>
                <option value="price">Sort by Price</option>
            </select>

            <ul>
                {filteredAndSortedItems.map(item => (
                    <li key={item.id}>
                        {item.name} - ${item.price}
                    </li>
                ))}
            </ul>
        </div>
    );
}

📝 Chapter Summary

State management is the core of React applications. Properly using state enables building responsive and interactive user interfaces.

Key Takeaways

  • ✅ useState Hook is used to manage component state
  • ✅ State updates are asynchronous, use functional updates to ensure correctness
  • ✅ Object and array state require immutable updates
  • ✅ useReducer is suitable for complex state logic
  • ✅ Custom Hooks can encapsulate state logic
  • ✅ State splitting and caching can optimize performance

Best Practices

  1. Keep state minimal: Only store necessary state
  2. Flatten state structure: Avoid deep nesting
  3. Use functional updates: Ensure correctness of state updates
  4. Split state reasonably: Improve component performance
  5. Encapsulate complex logic: Use custom Hooks

Continue Learning: Next Chapter - React Event Handling

Content is for learning and research only.