Unit 6 - Notes
INT252
Unit 6: Redux, Debugging and Deployment
1. Redux Basics
What is Redux?
Redux is a predictable state management container for JavaScript apps. While it can be used with any view library, it is most commonly associated with React. It helps manage the state of an application in a single place, keeping changes in your app predictable and traceable.
Why use Redux?
In standard React, data is passed top-down via props ("prop drilling"). As an application grows:
- Passing data through multiple layers of components that don't need it becomes unmanageable.
- Sibling components cannot easily share state without lifting state up to a common ancestor, often resulting in a massive root component.
- Redux solves this by creating a global store accessible by any component, regardless of its position in the component tree.
The Three Core Principles
- Single Source of Truth: The state of your whole application is stored in an object tree within a single store.
- State is Read-Only: The only way to change the state is to emit an action, an object describing what happened.
- Changes are made with Pure Functions: To specify how the state tree is transformed by actions, you write reducers.
2. App Starting Point for Redux
To integrate Redux into a React application, you typically need two main libraries:
- redux: The core state management library.
- react-redux: The official binding library that allows React components to interact with the Redux store.
Installation
npm install redux react-redux
Standard Folder Structure
Organizing Redux code is crucial for scalability. A common pattern is:
src/store.js(Setup of the store)src/actions/(Action creators)src/reducers/(Reducer logic)src/constants/(Action types)
3. Understanding the Redux Flow
Redux follows a strict unidirectional data flow. This means all data in an application follows the same lifecycle pattern, making the logic of your app more predictable.
The Cycle
- User Interaction: A user clicks a button in the View (Component).
- Dispatch Action: The component calls a
dispatch()function with an Action. - Reducer Processing: The Store runs the Reducer function with the current state and the dispatched action.
- State Update: The Reducer returns a new state object.
- View Update: The Store notifies the View (via
useSelectororconnect) that the state has changed, and the UI re-renders.
4. Store and Reducer
The Reducer
A reducer is a pure function that takes the previous state and an action, and returns the next state.
Key Rules for Reducers:
- Never mutate the arguments (state). Always return a new object/array (e.g., use
...spreadoperator). - Never perform side effects (API calls, routing transitions) inside a reducer.
Example: counterReducer.js
// Initial State
const initialState = {
count: 0
};
// Reducer Function
const counterReducer = (state = initialState, action) => {
switch (action.type) {
case 'INCREMENT':
// Returns a NEW object, does not modify 'state' directly
return {
...state,
count: state.count + 1
};
case 'DECREMENT':
return {
...state,
count: state.count - 1
};
default:
// If unknown action, return existing state
return state;
}
};
export default counterReducer;
The Store
The Store is the object that brings actions and reducers together. It has the following responsibilities:
- Holds application state.
- Allows access to state via
getState(). - Allows state to be updated via
dispatch(action). - Registers listeners via
subscribe(listener).
Creating the Store (store.js):
import { createStore } from 'redux';
import counterReducer from './reducers/counterReducer';
const store = createStore(counterReducer);
export default store;
Note: In complex apps, you might use combineReducers to manage multiple reducer files.
5. Connecting Components
To connect React components to the Redux store, we use the react-redux library. This is done in two steps: Providing the store and then accessing it in components.
Step 1: The Provider
The <Provider> component makes the Redux store available to any nested components that need to access the Redux store. This is usually done in index.js or App.js.
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import store from './store';
import App from './App';
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
Step 2: Accessing State (useSelector)
Modern React Redux uses Hooks. useSelector allows you to extract data from the Redux store state.
import { useSelector } from 'react-redux';
const CounterDisplay = () => {
// Accessing specific part of the state
const count = useSelector(state => state.count);
return <h1>Current Count: {count}</h1>;
};
6. Dispatching Actions
Actions
Actions are payloads of information that send data from your application to your store. They are plain JavaScript objects. Actions must have a type property that indicates the type of action being performed.
Action Structure:
{
type: 'ADD_TODO',
payload: { text: 'Buy milk' } // Optional data needed for update
}
Action Creators
To avoid writing action objects manually every time, we use Action Creators (functions that return actions).
// actions/index.js
export const increment = () => {
return {
type: 'INCREMENT'
};
};
export const addTodo = (text) => {
return {
type: 'ADD_TODO',
payload: text
}
}
Dispatching in Components (useDispatch)
The useDispatch hook returns a reference to the dispatch function from the Redux store. You use this to dispatch actions as needed.
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { increment } from './actions'; // Import action creator
const CounterControls = () => {
const dispatch = useDispatch();
return (
<button onClick={() => dispatch(increment())}>
Add 1
</button>
);
};
7. Building the App (Integration)
When building a React App with Redux, the development workflow typically follows this order:
- Design the State Shape: Decide what data needs to live in Redux (e.g., User object, List of items, UI loading states).
- Define Action Types: Create constants for event names (e.g.,
const FETCH_USER_SUCCESS = 'FETCH_USER_SUCCESS'). - Write Action Creators: Create functions to trigger these types.
- Write Reducers: Handle the logic for how state changes based on these actions.
- Setup Store: Combine reducers and create the store.
- Connect Components: Use
useSelectorto read data anduseDispatchto trigger changes.
Example Workflow:
If you are building a "Dark Mode" toggle:
- State:
{ isDarkMode: false } - Action:
TOGGLE_THEME - Reducer: If action is
TOGGLE_THEME, return{ isDarkMode: !state.isDarkMode }. - Component: A button calls
dispatch({type: 'TOGGLE_THEME'}). The main layout container usesuseSelectorto checkisDarkModeand applies CSS classes accordingly.
8. Debugging the React App
Debugging is a critical skill. In a React-Redux environment, errors usually fall into logic errors (wrong state updates) or rendering errors.
Redux DevTools Extension
This is the most powerful tool for debugging Redux.
- State Chart: Visualizes your state tree.
- Action Log: Shows every action dispatched, the time it happened, and the content of the action.
- Diff: Shows exactly what changed in the state after a specific action.
- Time Travel: Allows you to jump back to previous states to see how the UI looked before an action occurred.
Enabling DevTools:
const store = createStore(
reducer,
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);
React Developer Tools (Browser Extension)
- Components Tab: Inspects the component hierarchy (tree), props, state, and hooks for any component.
- Profiler Tab: Records interaction to measure performance (rendering time) to identify bottlenecks.
Common Debugging Techniques
- Console Logging: Use
console.login the render method to check if props are being received, or insideuseEffectto track updates. - Debugger Statement: Place the keyword
debugger;in your code. If the DevTools are open, execution will pause there, allowing you to inspect variables step-by-step. - Key Warning: If lists aren't rendering correctly, check the console for "Warning: Each child in a list should have a unique 'key' prop."
9. Deployment
Deployment involves moving the code from a local development environment to a production server where users can access it.
The Build Process
React apps cannot be deployed as raw source code (JSX/ES6) because browsers cannot read them efficiently. We must create a production build.
Command:
npm run build
What this does:
- Transpiling: Converts modern JavaScript (ES6+) and JSX into standard ES5 JavaScript compatible with all browsers.
- Bundling: Merges all files into a few small files (chunks) to reduce HTTP requests.
- Minification: Removes whitespace, comments, and shortens variable names to reduce file size.
- Output: Creates a
buildfolder containing static files (index.html,main.js,main.css).
Hosting Options
Since the result of npm run build is just static files (HTML/CSS/JS), you can host a React app anywhere:
- GitHub Pages: Good for static sites/portfolios.
- Netlify / Vercel: specialized for frontend frameworks; offer Continuous Deployment (CD) automatically when you push to Git.
- Cloud Providers (AWS S3 / Azure Static Web Apps): Scalable enterprise solutions.
- Traditional Web Server (Apache/Nginx): Configure the server to serve
index.htmlfor all routes (essential for React Router to work on refresh).