React useState Hook: Managing State with useState(true)

React useState Hook: Managing State with useState(true)

The useState hook manages component state:

import { useState } from 'react';

function ToggleButton() {
    const [isOn, setIsOn] = useState(false);
    
    return (
        <button onClick={() => setIsOn(!isOn)}>
            {isOn ? 'ON' : 'OFF'}
        </button>
    );
}

useState(false) creates state initialized to false. setIsOn updates it.

Basic Syntax

const [state, setState] = useState(initialValue);

Returns an array with two elements:

  • state: Current state value
  • setState: Function to update state

Array destructuring lets you name them anything:

const [count, setCount] = useState(0);
const [name, setName] = useState('');
const [isOpen, setIsOpen] = useState(false);

Updating State

Direct value:

setCount(5);  // Set count to 5
setName('Alice');
setIsOpen(true);

Function update (based on previous state):

setCount(prevCount => prevCount + 1);
setName(prevName => prevName.toUpperCase());

Use function updates when new state depends on old state. This prevents stale state bugs.

State Updates Are Asynchronous

State doesn't update immediately:

const [count, setCount] = useState(0);

function handleClick() {
    setCount(count + 1);
    console.log(count);  // Still 0 - hasn't updated yet
}

React batches state updates for performance. The component re-renders after the event handler completes.

Multiple Updates in Same Render

Multiple setState calls in the same handler are batched:

function handleClick() {
    setCount(count + 1);
    setCount(count + 1);
    setCount(count + 1);
    // count increases by 1, not 3
}

All three use the same count value. Use function updates:

function handleClick() {
    setCount(c => c + 1);
    setCount(c => c + 1);
    setCount(c => c + 1);
    // count increases by 3
}

Initial State

Static value:

const [count, setCount] = useState(0);
const [user, setUser] = useState(null);
const [items, setItems] = useState([]);

Lazy initialization (for expensive computation):

const [data, setData] = useState(() => {
    return expensiveComputation();
});

The function runs only once on mount. Use this when initial state requires calculation.

State Types

Boolean:

const [isLoading, setIsLoading] = useState(false);
const [isOpen, setIsOpen] = useState(true);

Number:

const [count, setCount] = useState(0);
const [age, setAge] = useState(25);

String:

const [name, setName] = useState('');
const [email, setEmail] = useState('');

Array:

const [items, setItems] = useState([]);
const [users, setUsers] = useState([{ id: 1, name: 'Alice' }]);

Object:

const [user, setUser] = useState({ name: '', email: '' });
const [form, setForm] = useState({ username: '', password: '' });

Updating Objects and Arrays

State updates must be immutable. Don't mutate:

// Wrong - mutates state
const [user, setUser] = useState({ name: 'Alice', age: 25 });
user.age = 26;  // Don't do this
setUser(user);  // React won't detect change

// Correct - create new object
setUser({ ...user, age: 26 });

For arrays:

// Wrong
items.push(newItem);
setItems(items);

// Correct
setItems([...items, newItem]);

// Removing item
setItems(items.filter(item => item.id !== idToRemove));

// Updating item
setItems(items.map(item => 
    item.id === targetId ? { ...item, updated: true } : item
));

Form Inputs

Controlled components use useState for input values:

function Form() {
    const [name, setName] = useState('');
    const [email, setEmail] = useState('');
    
    function handleSubmit(e) {
        e.preventDefault();
        console.log({ name, email });
    }
    
    return (
        <form onSubmit={handleSubmit}>
            <input
                value={name}
                onChange={e => setName(e.target.value)}
            />
            <input
                value={email}
                onChange={e => setEmail(e.target.value)}
            />
            <button>Submit</button>
        </form>
    );
}

Multiple State Variables vs Single Object

Two approaches:

Multiple state variables:

const [name, setName] = useState('');
const [email, setEmail] = useState('');
const [age, setAge] = useState(0);

Single object:

const [user, setUser] = useState({
    name: '',
    email: '',
    age: 0
});

// Update one field
setUser({ ...user, email: 'new@email.com' });

Use multiple variables for unrelated state. Use objects when state is related and often updated together.

Resetting State

Set back to initial value:

const [count, setCount] = useState(0);

function reset() {
    setCount(0);
}

For complex initial state, extract to variable:

const initialState = { name: '', email: '', age: 0 };
const [user, setUser] = useState(initialState);

function reset() {
    setUser(initialState);
}

Conditional State Updates

Only update if value changed:

function handleChange(newValue) {
    if (newValue !== value) {
        setValue(newValue);
    }
}

React optimizes this internally, but explicit checks can prevent unnecessary re-renders in child components.

State with useEffect

Common pattern—fetch data and store in state:

function UserProfile({ userId }) {
    const [user, setUser] = useState(null);
    const [loading, setLoading] = useState(true);
    
    useEffect(() => {
        setLoading(true);
        fetch(`/api/users/${userId}`)
            .then(r => r.json())
            .then(data => {
                setUser(data);
                setLoading(false);
            });
    }, [userId]);
    
    if (loading) return <div>Loading...</div>;
    return <div>{user.name}</div>;
}

Derived State

Don't store values you can calculate:

// Bad - storing derived value
const [items, setItems] = useState([]);
const [itemCount, setItemCount] = useState(0);

// Update both when adding item
setItems([...items, newItem]);
setItemCount(itemCount + 1);

// Good - calculate on the fly
const [items, setItems] = useState([]);
const itemCount = items.length;

useState vs useReducer

For complex state logic, use useReducer:

// Simple counter - useState is fine
const [count, setCount] = useState(0);

// Complex state with multiple actions - useReducer better
const [state, dispatch] = useReducer(reducer, initialState);

TypeScript with useState

Type inference usually works:

const [count, setCount] = useState(0);  // number
const [name, setName] = useState('');   // string

Explicit typing when needed:

const [user, setUser] = useState<User | null>(null);

Common Mistakes

Mutating state:

// Wrong
user.name = 'Bob';
setUser(user);

// Correct
setUser({ ...user, name: 'Bob' });

Reading state immediately after setting:

// Wrong
setCount(count + 1);
console.log(count);  // Still old value

// Correct - use effect
useEffect(() => {
    console.log(count);  // Updated value
}, [count]);

Not using function updates with dependencies:

// Problematic if count is stale
setInterval(() => setCount(count + 1), 1000);

// Better
setInterval(() => setCount(c => c + 1), 1000);

Further Reading

React's useState documentation covers all behaviors and examples.

The React docs on state explain when and how to use state in components.

Dan Abramov's guide to useEffect also covers useState patterns.

useState is the foundation of state management in functional React components.

Wear the code

Product mockup

useState(true); Developer T-Shirt (React Edition — Dark Mode)

£25.00

View product
Product mockup

useState(true); Developer T-Shirt (React Edition — Light Mode)

£25.00

View product

0 comments

Leave a comment

Please note, comments need to be approved before they are published.