🚀 LEVEL UP TO SENIOR:Unlock 500+ Advanced Practical Challenges & Exercises.
🎓 COURSERA PARTNER:Earn professional Google, Meta, and IBM certificates to supercharge your resume.
REFERENCEreact

react Documentation

LOADING ENGINE...

Reducers

AI & DATA SCIENCE // reducers

A reducer is a pure function that takes the current state and a dispatched action, and returns the new state, forming the core state-update logic in Redux.

Syntax

function reducer(state = initialState, action) {
  switch (action.type) {
    case 'SOME_ACTION': return { ...state, /* changes */ };
    default: return state;
  }
}

Deep Dive Course

A reducer's signature is always (state, action) => newState, and it must be a pure function: given the same state and action inputs, it must always return the same new state output, with no side effects like API calls, random values, or mutations of the existing state object performed inside it. When an action doesn't match anything the reducer cares about, the convention is to return the existing state completely unchanged via a default case, rather than returning undefined or something else — the name reducer itself borrows from JavaScript's Array.prototype.reduce(), since Redux essentially reduces a whole sequence of dispatched actions down into a single, current state value over time.

1Understanding Reducers

A reducer's signature is always (state, action) => newState, and it must be a pure function: given the same state and action inputs, it must always return the same new state output, with no side effects like API calls, random values, or mutations of the existing state object performed inside it. When an action doesn't match anything the reducer cares about, the convention is to return the existing state completely unchanged via a default case, rather than returning undefined or something else — the name reducer itself borrows from JavaScript's Array.prototype.reduce(), since Redux essentially reduces a whole sequence of dispatched actions down into a single, current state value over time.

💡

Never mutate the existing state object directly inside a reducer, like state.value = 5 or state.items.push(newItem) — always return a brand-new object/array with the needed changes, like { ...state, value: 5 }, since Redux and React rely on detecting state changes via reference comparison.

editor.html
function counterReducer(state = { count: 0 }, action) {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 };
    case 'decrement':
      return { count: state.count - 1 };
    default:
      return state;
  }
}

console.log(counterReducer({ count: 5 }, { type: 'increment' }));
localhost:3000

2Practical Example

Here is a real-world application of Reducers showing how it is used in production React code.

editor.html
function todosReducer(state = [], action) {
  switch (action.type) {
    case 'add_todo':
      return [...state, { text: action.payload, done: false }];
    default:
      return state;
  }
}

console.log(todosReducer([], { type: 'add_todo', payload: 'Buy milk' }));
localhost:3000

3Best Practices

Follow these guidelines when working with Reducers:

1. Keep reducers pure: no API calls, no random values, no direct mutation of the existing state, computing and returning a new state object based only on the given state and action

2. Always include a default case that returns the existing state unchanged, for any action type the reducer doesn't specifically handle

3. Use the spread operator, or a library like Immer via Redux Toolkit, to produce a new state object/array without mutating the original, rather than modifying nested properties in place

⚠️

Tip: Never mutate the existing state object directly inside a reducer, like state.value = 5 or state.items.push(newItem) — always return a brand-new object/array with the needed changes, like { ...state, value: 5 }, since Redux and React rely on detecting state changes via reference comparison.

editor.html
function counterReducer(state = { count: 0 }, action) {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 };
    case 'decrement':
      return { count: state.count - 1 };
    default:
      return state;
  }
}

console.log(counterReducer({ count: 5 }, { type: 'increment' }));
localhost:3000

Examples

Example 01Basic Usage
function counterReducer(state = { count: 0 }, action) {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 };
    case 'decrement':
      return { count: state.count - 1 };
    default:
      return state;
  }
}

console.log(counterReducer({ count: 5 }, { type: 'increment' }));
Example 02Advanced Example
function todosReducer(state = [], action) {
  switch (action.type) {
    case 'add_todo':
      return [...state, { text: action.payload, done: false }];
    default:
      return state;
  }
}

console.log(todosReducer([], { type: 'add_todo', payload: 'Buy milk' }));

Best Practices

  • Keep reducers pure: no API calls, no random values, no direct mutation of the existing state, computing and returning a new state object based only on the given state and action
  • Always include a default case that returns the existing state unchanged, for any action type the reducer doesn't specifically handle
  • Use the spread operator, or a library like Immer via Redux Toolkit, to produce a new state object/array without mutating the original, rather than modifying nested properties in place

Interview Question

Why must a reducer return a new state object rather than mutating and returning the existing state object in place, even if the mutation would produce the logically correct final values?

Hint: Think about how Redux, and React via useSelector, actually detect that state has changed — by comparing object references, or by deeply checking every property's value?

Redux, and react-redux's useSelector hook feeding into React's own re-render logic, detect whether state has changed by comparing the previous state object's reference against the new one, essentially a === check, rather than deeply inspecting every property inside the object for changes, since a deep comparison on every single state update across a potentially large state tree would be considerably more expensive. If a reducer mutated the existing state object in place and then returned that exact same object reference, the reference comparison would see no change at all, since it's literally the same object in memory, even though its internal properties were actually modified — this would cause components depending on that state to incorrectly fail to re-render, since Redux's change-detection would report nothing changed. Returning a genuinely new object, or array, whenever something meaningful actually changes ensures the reference itself changes too, which is exactly the signal Redux and React rely on to correctly detect that a re-render is needed.

Exercises

MediumPractice using Reducers in a real scenario.
View Solution
function counterReducer(state = { count: 0 }, action) {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 };
    case 'decrement':
      return { count: state.count - 1 };
    default:
      return state;
  }
}

console.log(counterReducer({ count: 5 }, { type: 'increment' }));

Frequently Asked Questions

Why must a reducer return a new state object rather than mutating and returning the existing state object in place, even if the mutation would produce the logically correct final values?

Redux, and react-redux's useSelector hook feeding into React's own re-render logic, detect whether state has changed by comparing the previous state object's reference against the new one, essentially a === check, rather than deeply inspecting every property inside the object for changes, since a deep comparison on every single state update across a potentially large state tree would be considerably more expensive. If a reducer mutated the existing state object in place and then returned that exact same object reference, the reference comparison would see no change at all, since it's literally the same object in memory, even though its internal properties were actually modified — this would cause components depending on that state to incorrectly fail to re-render, since Redux's change-detection would report nothing changed. Returning a genuinely new object, or array, whenever something meaningful actually changes ensures the reference itself changes too, which is exactly the signal Redux and React rely on to correctly detect that a re-render is needed.

Related Functions

actionsstoreredux