Redux keeps an app's state in a single object tree, changed only through a strict, predictable pattern — which sounds like extra ceremony until an app's state gets tangled enough that "who changed this and when" becomes a real question.
Every Redux app is built from the same three ideas: a store holding the state, actions describing what happened, and a reducer that computes the new state from the old state plus an action.
import { createSlice, configureStore } from '@reduxjs/toolkit'
const counterSlice = createSlice({
name: 'counter',
initialState: { value: 0 },
reducers: {
incremented: state => { state.value += 1 },
decremented: state => { state.value -= 1 }
}
})
export const { incremented, decremented } = counterSlice.actions
const store = configureStore({ reducer: counterSlice.reducer })
Redux Toolkit is the modern, recommended way to write Redux — it looks like you're mutating state directly, but under the hood it's using Immer to produce a proper immutable update, avoiding the boilerplate older Redux code required.
import { useSelector, useDispatch } from 'react-redux'
import { incremented } from './counterSlice'
function Counter() {
const count = useSelector(state => state.value)
const dispatch = useDispatch()
return <button onClick={() => dispatch(incremented())}>{count}</button>
}
useSelector reads a slice of state and re-renders the component only when that slice changes; dispatch is the only way to trigger a state change — components never mutate state directly.
useState and useContext cover it with far less setup. Reach for Redux when state is genuinely global, updated from many places, and you need to trace exactly how it changed over time.
Because every state change goes through a dispatched action and a pure reducer function, Redux DevTools can record every action in order and let you "time travel" backward through state history — a debugging capability that's much harder to build without this strict discipline.
Comments