# Redux — Part 1: Store, Actions, Reducers

## 1. The Problem Redux Solves

React's built-in state (`useState`, `useContext`) works well for state that's local to a component or shared within a small subtree. As an app grows, state that many unrelated components need to read and update (the logged-in user, a shopping cart, notification state) becomes awkward to thread through props or juggle across multiple Context providers. Redux is a state-management library built on one strict rule: **all shared application state lives in a single store, and the only way to change it is by dispatching a plain object describing what happened.**

```
Component-local state  → useState, stays inside one component's tree
Redux state             → ONE store, readable/writable from anywhere, changes only via dispatch
```

## 2. The Store: a Single Source of Truth

```ts
import { createStore } from "redux";

interface AppState {
  count: number;
}

const initialState: AppState = { count: 0 };

function counterReducer(state = initialState, action: { type: string }): AppState {
  switch (action.type) {
    case "INCREMENT":
      return { count: state.count + 1 };
    case "DECREMENT":
      return { count: state.count - 1 };
    default:
      return state;
  }
}

const store = createStore(counterReducer);

store.subscribe(() => console.log("state changed:", store.getState()));
store.dispatch({ type: "INCREMENT" }); // logs: state changed: { count: 1 }
```

There is exactly **one** store per app, holding the entire shared state tree. `store.getState()` reads it, `store.dispatch(action)` is the only way to request a change, and `store.subscribe(listener)` registers a callback fired after every dispatch — this triple (`getState`/`dispatch`/`subscribe`) is the entire public surface a reducer-driven UI is built on.

## 3. Actions: Plain Objects Describing "What Happened"

```ts
// an action is JUST a plain object with a `type` field — nothing more required
const increment = { type: "INCREMENT" };
const addTodo = { type: "todos/add", payload: { text: "Buy milk" } };

// an "action creator" is just a function that returns one
function addTodoAction(text: string) {
  return { type: "todos/add", payload: { text } };
}
```

An action describes an *event that already happened* ("the user clicked increment," "a todo was added"), not a command to mutate state directly — the reducer is what decides how that event changes state. By convention, `type` is a string (often namespaced like `"todos/add"`) and any additional data rides in a `payload` field, though Redux itself enforces nothing about the shape beyond requiring a `type`.

## 4. Reducers: Pure Functions From (state, action) to New State

```ts
function todosReducer(state: Todo[] = [], action: { type: string; payload?: any }): Todo[] {
  switch (action.type) {
    case "todos/add":
      return [...state, { id: Date.now(), text: action.payload.text, done: false }];
    case "todos/toggle":
      return state.map((t) => (t.id === action.payload.id ? { ...t, done: !t.done } : t));
    case "todos/remove":
      return state.filter((t) => t.id !== action.payload.id);
    default:
      return state; // MUST return the existing state for unrecognized actions
  }
}
```

A reducer takes the current state and an action, and returns the **next** state — never mutating the state it was given. Every branch that doesn't apply returns the original `state` reference unchanged (the `default` case), which is what lets Redux (and React-Redux's `useSelector`, covered next) cheaply detect "did this part of the tree actually change" via reference equality rather than deep comparison.

## 5. Combining Reducers for a Larger State Tree

```ts
import { combineReducers, createStore } from "redux";

const rootReducer = combineReducers({
  counter: counterReducer,
  todos: todosReducer,
});

const store = createStore(rootReducer);
// store.getState() now looks like: { counter: { count: 0 }, todos: [] }
```

`combineReducers` builds one root reducer out of many smaller ones, each responsible for its own **slice** of the overall state tree, keyed by the object property name it's assigned to. Each slice reducer only ever sees and returns its own slice — `todosReducer` has no idea `counter` exists — which is what lets a large app's state management stay organized as many small, independently testable reducers instead of one giant function.

## 6. Connecting to React: `useSelector` and `useDispatch`

```tsx
import { Provider, useSelector, useDispatch } from "react-redux";

function Counter() {
  const count = useSelector((state: RootState) => state.counter.count);
  const dispatch = useDispatch();

  return (
    <div>
      <p>{count}</p>
      <button onClick={() => dispatch({ type: "INCREMENT" })}>+</button>
    </div>
  );
}

function App() {
  return (
    <Provider store={store}>
      <Counter />
    </Provider>
  );
}
```

`react-redux` (a separate package from `redux` itself) supplies the React bindings: `<Provider store={store}>` makes the store available via context to the whole tree, `useSelector` subscribes a component to a specific **slice** of state (re-rendering only when the selected value changes, by reference equality — this is exactly why reducers must return new references only when data actually changed), and `useDispatch` returns the store's `dispatch` function for firing actions from event handlers.

## Common Pitfalls

1. **Mutating state directly inside a reducer** (`state.count++` or `state.push(item)`) instead of returning a new object/array — breaks the reference-equality change detection `useSelector` and Redux's own internals depend on, causing silent "state changed but nothing re-rendered" bugs.
2. **Forgetting the `default: return state` case** — an unhandled action type falling through with no return produces `undefined` as the new state, corrupting that slice.
3. **Doing async work (an API call) directly inside a reducer** — reducers must be pure and synchronous; async logic belongs in middleware (Part 2) or, in modern Redux Toolkit code, `createAsyncThunk`.
4. **Selecting more state than needed in `useSelector`** (e.g. the whole state tree instead of one slice) — causes the component to re-render on every unrelated state change, defeating the fine-grained subscription model.
5. **Putting state that's genuinely local to one component into Redux** — not everything needs to be global; a form input's in-progress value rarely needs to live in the shared store.

## Quick Reference

```
createStore(reducer)                      → one store, holds the whole state tree
store.getState() / .dispatch(action) / .subscribe(fn)  → the entire store API

action  = { type: string, payload?: any }  → plain object, describes what happened
reducer = (state, action) => newState      → pure function, NEVER mutates, always
                                              returns unchanged state for unknown actions

combineReducers({ slice1: r1, slice2: r2 }) → one root reducer from many slice reducers

<Provider store={store}>                    → makes the store available via context
useSelector((state) => state.slice.value)   → subscribe to one slice, re-render on change
useDispatch()                                → returns dispatch, used in event handlers
```

Part 2 covers middleware (why reducers alone can't handle async), the purity requirement in more depth, and why Redux Toolkit exists.
