Tutorials Logic, IN +91 8092939553 info@tutorialslogic.com
FAQs Support
Navigation
Home About Us Contact Us Blogs FAQs
Tutorials
All Tutorials
Services
Academic Projects Resume Writing Interview Questions Website Development
Compiler Tutorials

useReducer Hook

What is useReducer?

useReducer is an alternative to useState for managing complex state logic. It follows the Redux pattern: state is updated by dispatching actions to a reducer function, which returns the new state.

FeatureuseStateuseReducer
Best forSimple, independent state valuesComplex state with multiple sub-values
Update logicInline in componentCentralized in reducer function
TestabilityHarder to test logicReducer is a pure function — easy to test
Next state depends on previousFunctional update: setState(prev => ...)Natural — reducer receives current state
useReducer — Counter, Todo List, Form State
import { useReducer } from 'react'

// 1. Define the reducer — pure function (state, action) => newState
function counterReducer(state, action) {
    switch (action.type) {
        case 'INCREMENT':
            return { count: state.count + 1 }
        case 'DECREMENT':
            return { count: state.count - 1 }
        case 'INCREMENT_BY':
            return { count: state.count + action.payload }
        case 'RESET':
            return { count: 0 }
        default:
            throw new Error(`Unknown action: ${action.type}`)
    }
}

// 2. Initial state
const initialState = { count: 0 }

function Counter() {
    // 3. useReducer(reducer, initialState) → [state, dispatch]
    const [state, dispatch] = useReducer(counterReducer, initialState)

    return (
        <div>
            <p>Count: {state.count}</p>

            {/* 4. Dispatch actions */}
            <button onClick={() => dispatch({ type: 'INCREMENT' })}>+1</button>
            <button onClick={() => dispatch({ type: 'DECREMENT' })}>-1</button>
            <button onClick={() => dispatch({ type: 'INCREMENT_BY', payload: 5 })}>+5</button>
            <button onClick={() => dispatch({ type: 'RESET' })}>Reset</button>
        </div>
    )
}

// Reducer is a pure function — easy to test!
// counterReducer({ count: 5 }, { type: 'INCREMENT' }) → { count: 6 }
// counterReducer({ count: 0 }, { type: 'RESET' })     → { count: 0 }
import { useReducer } from 'react'

// Action types as constants — prevents typos
const TODO_ACTIONS = {
    ADD:    'ADD',
    TOGGLE: 'TOGGLE',
    DELETE: 'DELETE',
    CLEAR_DONE: 'CLEAR_DONE',
}

function todoReducer(state, action) {
    switch (action.type) {
        case TODO_ACTIONS.ADD:
            return {
                ...state,
                todos: [...state.todos, {
                    id: Date.now(),
                    text: action.payload,
                    done: false
                }]
            }
        case TODO_ACTIONS.TOGGLE:
            return {
                ...state,
                todos: state.todos.map(todo =>
                    todo.id === action.payload
                        ? { ...todo, done: !todo.done }
                        : todo
                )
            }
        case TODO_ACTIONS.DELETE:
            return {
                ...state,
                todos: state.todos.filter(todo => todo.id !== action.payload)
            }
        case TODO_ACTIONS.CLEAR_DONE:
            return {
                ...state,
                todos: state.todos.filter(todo => !todo.done)
            }
        default:
            return state
    }
}

const initialTodoState = { todos: [] }

function TodoApp() {
    const [state, dispatch] = useReducer(todoReducer, initialTodoState)
    const [input, setInput] = React.useState('')

    const addTodo = () => {
        if (!input.trim()) return
        dispatch({ type: TODO_ACTIONS.ADD, payload: input })
        setInput('')
    }

    return (
        <div>
            <input value={input} onChange={e => setInput(e.target.value)} />
            <button onClick={addTodo}>Add</button>
            <button onClick={() => dispatch({ type: TODO_ACTIONS.CLEAR_DONE })}>
                Clear Done
            </button>
            <ul>
                {state.todos.map(todo => (
                    <li key={todo.id}>
                        <span
                            style={{ textDecoration: todo.done ? 'line-through' : 'none' }}
                            onClick={() => dispatch({ type: TODO_ACTIONS.TOGGLE, payload: todo.id })}
                        >
                            {todo.text}
                        </span>
                        <button onClick={() => dispatch({ type: TODO_ACTIONS.DELETE, payload: todo.id })}>
                            ✕
                        </button>
                    </li>
                ))}
            </ul>
            <p>{state.todos.filter(t => t.done).length} / {state.todos.length} done</p>
        </div>
    )
}
import { useReducer } from 'react'

// Complex form state with useReducer
function formReducer(state, action) {
    switch (action.type) {
        case 'SET_FIELD':
            return {
                ...state,
                values: { ...state.values, [action.field]: action.value },
                errors: { ...state.errors, [action.field]: '' }  // clear error on change
            }
        case 'SET_ERROR':
            return {
                ...state,
                errors: { ...state.errors, [action.field]: action.error }
            }
        case 'SET_ERRORS':
            return { ...state, errors: action.errors }
        case 'SET_SUBMITTING':
            return { ...state, isSubmitting: action.value }
        case 'RESET':
            return initialFormState
        default:
            return state
    }
}

const initialFormState = {
    values: { name: '', email: '', password: '' },
    errors: { name: '', email: '', password: '' },
    isSubmitting: false,
}

function RegistrationForm() {
    const [state, dispatch] = useReducer(formReducer, initialFormState)
    const { values, errors, isSubmitting } = state

    const handleChange = (e) => {
        dispatch({ type: 'SET_FIELD', field: e.target.name, value: e.target.value })
    }

    const validate = () => {
        const errs = {}
        if (!values.name)    errs.name = 'Name is required'
        if (!values.email)   errs.email = 'Email is required'
        if (values.password.length < 8) errs.password = 'Min 8 characters'
        return errs
    }

    const handleSubmit = async (e) => {
        e.preventDefault()
        const errs = validate()
        if (Object.keys(errs).length) {
            dispatch({ type: 'SET_ERRORS', errors: errs })
            return
        }
        dispatch({ type: 'SET_SUBMITTING', value: true })
        await new Promise(r => setTimeout(r, 1000))  // simulate API
        dispatch({ type: 'RESET' })
        alert('Registered!')
    }

    return (
        <form onSubmit={handleSubmit}>
            <input name="name" value={values.name} onChange={handleChange} placeholder="Name" />
            {errors.name && <span className="error">{errors.name}</span>}

            <input name="email" type="email" value={values.email} onChange={handleChange} placeholder="Email" />
            {errors.email && <span className="error">{errors.email}</span>}

            <input name="password" type="password" value={values.password} onChange={handleChange} placeholder="Password" />
            {errors.password && <span className="error">{errors.password}</span>}

            <button type="submit" disabled={isSubmitting}>
                {isSubmitting ? 'Registering...' : 'Register'}
            </button>
        </form>
    )
}

Ready to Level Up Your Skills?

Explore 500+ free tutorials across 20+ languages and frameworks.