Build Redux: Guide to State Management Mastery
Redux solves a big problem: managing state. State means data your app uses, like user info, loading status, or
Redux solves a big problem: managing state. State means data your app uses, like user info, loading status, or form inputs. As apps grow, state gets messy.
Redux keeps state organized in one central place. Think of it like a single notebook holding all app data. Every part of your app reads from this notebook. When changes happen, they update the notebook. No more lost data or confusing connections.
Why Build Redux Yourself?
Building Redux teaches you its magic. You’ll understand:
- How state updates safely
- Why changes are predictable
- What makes Redux reliable
This knowledge helps you debug apps faster and write cleaner code. Plus, you’ll appreciate libraries more when you see their inner workings.
Core Ideas Behind Redux
Redux stands on three rules:
- Single Source of Truth: All state lives in one object called the
store. - State is Read-Only: You can’t change state directly. Instead, you describe changes with plain objects called
actions. - Changes with Pure Functions: A function called a
reducertakes the current state and an action, then returns the new state.
Building the Redux Store
The store holds your state and handles updates. Here’s how to build it:
javascript
function createStore(reducer) {
let state;
let listeners = [];
const getState = () => state;
const dispatch = (action) => {
state = reducer(state, action);
listeners.forEach(listener => listener());
};
const subscribe = (listener) => {
listeners.push(listener);
return () => {
listeners = listeners.filter(l => l !== listener);
};
};
// Initialize state
dispatch({ type: '@@INIT' });
return { getState, dispatch, subscribe };
}
getState: Lets you read the current state.dispatch: Takes an action, runs it through the reducer to update state, then notifies listeners.subscribe: Adds functions (listeners) that trigger when state changes.
Creating Actions and Reducers
Actions are simple objects with a type (like “ADD_TODO”) and optional data:
javascript
{ type: 'ADD_TODO', text: 'Learn Redux' }
Reducers are pure functions that handle state changes:
javascript
function todoReducer(state = [], action) {
switch (action.type) {
case 'ADD_TODO':
return [...state, { text: action.text, completed: false }];
case 'TOGGLE_TODO':
return state.map((todo, index) =>
index === action.index
? { ...todo, completed: !todo.completed }
: todo
);
default:
return state;
}
}
Rules for reducers:
- Never change state directly. Create new copies.
- Return unchanged state for unknown actions.
Connecting Redux to React
Link your store to components using Provider and connect:
- Wrap your app with
Provider:
jsx
import { Provider } from 'react-redux';
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
- Connect components:
jsx
import { connect } from 'react-redux';
const TodoList = ({ todos, addTodo }) => (
// Component uses todos and addTodo from props
);
const mapStateToProps = state => ({
todos: state.todos
});
const mapDispatchToProps = dispatch => ({
addTodo: text => dispatch({ type: 'ADD_TODO', text })
});
export default connect(mapStateToProps, mapDispatchToProps)(TodoList);
Adding Middleware
Middleware lets you run code between dispatch and the reducer. Use it for logging, async calls, or error tracking.
Build a logger middleware:
javascript
const logger = store => next => action => {
console.log('Dispatching:', action);
let result = next(action);
console.log('New state:', store.getState());
return result;
};
Apply middleware to your store:
javascript
function applyMiddleware(...middlewares) {
return createStore => (reducer, initialState) => {
const store = createStore(reducer, initialState);
let dispatch = store.dispatch;
middlewares.forEach(middleware => {
dispatch = middleware(store)(dispatch);
});
return { ...store, dispatch };
};
}
// Create store with middleware
const store = createStore(
reducer,
applyMiddleware(logger)
);
Testing Your Redux Code
Test key parts:
- Reducers: Check if they handle actions correctly.
javascript
test('ADD_TODO adds a new todo', () => {
const stateBefore = [];
const action = { type: 'ADD_TODO', text: 'Test' };
const stateAfter = [{ text: 'Test', completed: false }];
expect(todoReducer(stateBefore, action)).toEqual(stateAfter);
});
- Actions: Verify they create the right objects.
- Middleware: Ensure they process actions properly.
Common Mistakes and Fixes
- Mutating State:
Wrong:state.todos.push(newTodo)
Right:return [...state.todos, newTodo]
Fix: Always copy arrays/objects before changing them. - Forgetting Default Case in Reducers:
Always returnstatefor unknown actions. - Overcomplicating Actions:
Keep actions focused. Use multiple simple actions instead of one giant action. - Too Much State in Redux:
Don’t put all component state in Redux. Use it only for data shared across your app. - Async Action Issues:
Use middleware likeredux-thunkfor async logic:javascriptCopyDownloadconst fetchData = () => dispatch => { dispatch({ type: ‘FETCH_START’ }); fetch(‘/data’).then(res => res.json()).then(data => { dispatch({ type: ‘FETCH_SUCCESS’, data }); }); };
By building Redux yourself, you gain control over state management. You’ll write apps that are easier to maintain, scale, and debug. Start small, test often, and keep your state predictable.



