Connecting Redux to React

Integrating Redux State Management with React Components

Introduction to React-Redux

While Redux can be used with any UI layer, it's most commonly paired with React. The official react-redux library provides bindings to connect Redux and React together efficiently.

flowchart TB ReactApp[React Application] subgraph Redux Store[Redux Store] Store --> State[Application State] Dispatch[Dispatch Actions] --> Store end subgraph ReactRedux Provider[Provider Component] Connect[connect / useSelector + useDispatch] end ReactApp --> Provider Provider --> Connect Connect --> ReactComponents[React Components] Connect --> |Reads State| State ReactComponents --> |Dispatches| Dispatch classDef redux fill:#f99,stroke:#933,stroke-width:2px; classDef reactredux fill:#9af,stroke:#36a,stroke-width:2px; classDef react fill:#9f9,stroke:#393,stroke-width:2px; class Store,State,Dispatch redux; class Provider,Connect reactredux; class ReactApp,ReactComponents react;

The react-redux library provides several key components:


// Installing react-redux
npm install react-redux

// or
yarn add react-redux
        

The Provider Component

The <Provider> component makes the Redux store available to all components in your application without passing it explicitly through props.


import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { createStore } from 'redux';
import rootReducer from './reducers';
import App from './App';

// Create the Redux store
const store = createStore(rootReducer);

// Wrap your application with the Provider
ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
);
        

The Provider uses React's Context API under the hood to make the store accessible to all components in the tree.

Real-World Analogy: Electric Grid

The Provider component is like the electric grid for a city:

  • The power station (Redux store) generates electricity (state)
  • The power grid (Provider) distributes electricity to the entire city
  • Individual buildings (components) can tap into the grid without running direct lines to the power station
  • Buildings don't need to know how electricity is generated, they just need to know how to connect to the grid

Just as you wouldn't want each building to have its own connection directly to the power station, you don't want each component to import and access the Redux store directly. The Provider creates a centralized distribution system.

The connect() Function

The connect() function is a higher-order component (HOC) that connects a React component to the Redux store.

Basic Structure


import { connect } from 'react-redux';

// Component definition
function MyComponent({ data, onButtonClick }) {
  return (
    <div>
      <h2>{data.title}</h2>
      <button onClick={onButtonClick}>Click Me</button>
    </div>
  );
}

// Map Redux state to component props
const mapStateToProps = (state, ownProps) => ({
  data: state.someData
});

// Map Redux actions to component props
const mapDispatchToProps = (dispatch, ownProps) => ({
  onButtonClick: () => dispatch({ type: 'SOME_ACTION' })
});

// Connect component to Redux
export default connect(
  mapStateToProps,
  mapDispatchToProps
)(MyComponent);
        

mapStateToProps

The mapStateToProps function determines which parts of the Redux state are exposed to your component as props.


// Basic mapStateToProps
const mapStateToProps = state => ({
  todos: state.todos,
  filter: state.visibilityFilter
});

// Using selectors in mapStateToProps
import { getVisibleTodos } from './selectors';

const mapStateToProps = state => ({
  todos: getVisibleTodos(state),
  filter: state.visibilityFilter
});

// Using component's own props (ownProps)
const mapStateToProps = (state, ownProps) => ({
  todo: state.todos.find(todo => todo.id === ownProps.todoId)
});
        

Best practices for mapStateToProps:

mapDispatchToProps

The mapDispatchToProps function lets you create callback props that dispatch actions when called.


// Object shorthand
const mapDispatchToProps = {
  addTodo: text => ({ type: 'ADD_TODO', payload: { text } }),
  toggleTodo: id => ({ type: 'TOGGLE_TODO', payload: { id } })
};

// Function form
const mapDispatchToProps = dispatch => ({
  addTodo: text => dispatch({ type: 'ADD_TODO', payload: { text } }),
  toggleTodo: id => dispatch({ type: 'TOGGLE_TODO', payload: { id } })
});

// Using action creators
import { addTodo, toggleTodo } from './actions';

// Object shorthand with action creators
const mapDispatchToProps = {
  addTodo,
  toggleTodo
};

// Function form with action creators
const mapDispatchToProps = dispatch => ({
  addTodo: text => dispatch(addTodo(text)),
  toggleTodo: id => dispatch(toggleTodo(id))
});
        

Best practices for mapDispatchToProps:

Real-World Example: E-commerce Product Page

Here's how Redux might connect to a product detail page in an e-commerce application:


import React, { useEffect } from 'react';
import { connect } from 'react-redux';
import { fetchProduct, addToCart } from './actions';
import LoadingSpinner from './LoadingSpinner';
import ErrorMessage from './ErrorMessage';

function ProductDetail({ 
  product, 
  loading, 
  error, 
  fetchProduct, 
  addToCart,
  match // from react-router
}) {
  const productId = match.params.id;
  
  useEffect(() => {
    fetchProduct(productId);
  }, [productId, fetchProduct]);
  
  if (loading) return <LoadingSpinner />;
  if (error) return <ErrorMessage message={error} />;
  if (!product) return <div>Product not found</div>;
  
  return (
    <div className="product-detail">
      <h1>{product.name}</h1>
      <div className="product-image">
        <img src={product.imageUrl} alt={product.name} />
      </div>
      <div className="product-info">
        <p className="price">${product.price.toFixed(2)}</p>
        <p className="description">{product.description}</p>
        <button 
          onClick={() => addToCart(product.id)}
          disabled={!product.inStock}
        >
          {product.inStock ? 'Add to Cart' : 'Out of Stock'}
        </button>
      </div>
    </div>
  );
}

const mapStateToProps = (state, ownProps) => {
  const productId = ownProps.match.params.id;
  return {
    product: state.products.items[productId],
    loading: state.products.loading,
    error: state.products.error
  };
};

const mapDispatchToProps = {
  fetchProduct,
  addToCart
};

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(ProductDetail);
          

This example shows how a real-world component would connect to Redux to fetch product data and dispatch actions like adding to cart.

React-Redux Hooks

In modern React applications with hooks, react-redux provides hook alternatives to connect().

useSelector

The useSelector hook lets you extract data from the Redux store state.


import React from 'react';
import { useSelector } from 'react-redux';

function TodoList() {
  // Extract data from Redux store
  const todos = useSelector(state => state.todos);
  const filter = useSelector(state => state.visibilityFilter);
  
  // Filter the todos based on the filter
  const visibleTodos = todos.filter(todo => {
    if (filter === 'SHOW_COMPLETED') return todo.completed;
    if (filter === 'SHOW_ACTIVE') return !todo.completed;
    return true; // SHOW_ALL
  });
  
  return (
    <ul>
      {visibleTodos.map(todo => (
        <li key={todo.id}>{todo.text}</li>
      ))}
    </ul>
  );
}
        

Key features of useSelector:


// With custom equality function
import { useSelector, shallowEqual } from 'react-redux';

function TodoList() {
  // Only re-render if the array reference changes or its contents change
  const todos = useSelector(state => state.todos, shallowEqual);
  
  // Rest of component...
}
        

useDispatch

The useDispatch hook gives you access to the Redux store's dispatch function.


import React, { useState } from 'react';
import { useDispatch } from 'react-redux';
import { addTodo } from './actions';

function AddTodo() {
  const [text, setText] = useState('');
  const dispatch = useDispatch();
  
  const handleSubmit = e => {
    e.preventDefault();
    if (!text.trim()) return;
    
    dispatch(addTodo(text));
    setText('');
  };
  
  return (
    <form onSubmit={handleSubmit}>
      <input 
        value={text} 
        onChange={e => setText(e.target.value)} 
        placeholder="Add a todo"
      />
      <button type="submit">Add</button>
    </form>
  );
}
        

useStore

The useStore hook returns a reference to the same Redux store that was passed to the <Provider>.


import { useStore } from 'react-redux';

function ComponentWithStoreAccess() {
  const store = useStore();
  
  // This is generally not recommended, but can be useful in rare cases
  const state = store.getState();
  
  return (
    <div>
      Store accessed directly: {state.someValue}
    </div>
  );
}
        

Hooks vs connect(): When to Use Each

Both approaches have their strengths:

Hooks API connect() API
More modern, fits with functional components Works with class components
Less boilerplate code More explicit about data dependencies
More flexible for custom logic Better performance optimizations out of the box
Easier to understand for developers familiar with hooks More established patterns and best practices

In modern applications, hooks are generally preferred for new code, but both approaches are valid and can even be mixed within the same application.

Performance Optimizations

Connecting Redux to React efficiently requires careful attention to performance to avoid unnecessary re-renders.

Selector Memoization

Use the reselect library to create memoized selectors that only recalculate when their inputs change.


import { createSelector } from 'reselect';
import { useSelector } from 'react-redux';

// Input selectors
const getTodos = state => state.todos;
const getFilter = state => state.visibilityFilter;

// Memoized selector
const getVisibleTodos = createSelector(
  [getTodos, getFilter],
  (todos, filter) => {
    switch (filter) {
      case 'SHOW_COMPLETED':
        return todos.filter(todo => todo.completed);
      case 'SHOW_ACTIVE':
        return todos.filter(todo => !todo.completed);
      default:
        return todos;
    }
  }
);

// Component using the memoized selector
function TodoList() {
  // Only recalculates when todos or filter changes
  const visibleTodos = useSelector(getVisibleTodos);
  
  return (
    <ul>
      {visibleTodos.map(todo => (
        <li key={todo.id}>{todo.text}</li>
      ))}
    </ul>
  );
}
        

React.memo

Wrap your connected components with React.memo to prevent re-renders when props haven't changed.


import React, { memo } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { toggleTodo } from './actions';

// Memoized component only re-renders when its props change
const TodoItem = memo(function TodoItem({ todo }) {
  const dispatch = useDispatch();
  
  return (
    <li
      onClick={() => dispatch(toggleTodo(todo.id))}
      style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}
    >
      {todo.text}
    </li>
  );
});

function TodoList() {
  const todos = useSelector(state => state.todos);
  
  return (
    <ul>
      {todos.map(todo => (
        <TodoItem key={todo.id} todo={todo} />
      ))}
    </ul>
  );
}
        

Equality Comparisons

Customize equality checks in useSelector to prevent unnecessary re-renders.


import { useSelector, shallowEqual } from 'react-redux';

function MyComponent() {
  // Using shallow equality check
  const { name, age } = useSelector(
    state => ({ 
      name: state.user.name, 
      age: state.user.age 
    }),
    shallowEqual // Prevents re-renders if name and age haven't changed
  );
  
  return (
    <div>
      Name: {name}, Age: {age}
    </div>
  );
}
        

Batch Updates

Redux dispatches that happen during React event handlers are automatically batched, but for other scenarios, you might need to batch manually.


import { batch } from 'react-redux';

function handleComplexOperation() {
  batch(() => {
    // All of these dispatches will be batched into one render update
    dispatch(action1());
    dispatch(action2());
    dispatch(action3());
  });
}
        

Performance Optimization Checklist

When connecting Redux to React, follow this checklist for optimal performance:

  1. Normalize state to avoid deep nested objects
  2. Use selectors for computing derived data
  3. Memoize selectors with reselect for expensive calculations
  4. Connect components at the right level - not too high, not too low in the component tree
  5. Use React.memo for connected components
  6. Consider shallowEqual for complex object comparisons
  7. Batch multiple dispatches when making several updates at once
  8. Avoid creating new objects or arrays in mapStateToProps or useSelector
  9. Use DevTools to identify and fix performance bottlenecks

Container and Presentational Components

A common pattern when using Redux with React is to separate "container" components (connected to Redux) from "presentational" components (pure UI).

flowchart TD Store[Redux Store] subgraph Containers["Container Components"] TodoListContainer TodoItemContainer FilterContainer end subgraph Presentation["Presentational Components"] TodoList TodoItem FilterButtons end Store --> Containers TodoListContainer --> TodoList TodoItemContainer --> TodoItem FilterContainer --> FilterButtons classDef store fill:#f99,stroke:#933,stroke-width:2px; classDef container fill:#9af,stroke:#36a,stroke-width:2px; classDef presentation fill:#9f9,stroke:#393,stroke-width:2px; class Store store; class TodoListContainer,TodoItemContainer,FilterContainer container; class TodoList,TodoItem,FilterButtons presentation;

Container Components

Presentational Components


// TodoListContainer.js - Container Component
import { connect } from 'react-redux';
import { toggleTodo } from '../actions';
import { getVisibleTodos } from '../selectors';
import TodoList from '../components/TodoList';

const mapStateToProps = state => ({
  todos: getVisibleTodos(state)
});

const mapDispatchToProps = {
  onTodoClick: toggleTodo
};

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(TodoList);

// TodoList.js - Presentational Component
import React from 'react';
import TodoItem from './TodoItem';

function TodoList({ todos, onTodoClick }) {
  return (
    <ul>
      {todos.map(todo => (
        <TodoItem
          key={todo.id}
          {...todo}
          onClick={() => onTodoClick(todo.id)}
        />
      ))}
    </ul>
  );
}

export default TodoList;
        

Benefits of this separation:

Real-World Analogy: Restaurant Organization

The container/presentational pattern is like the division of labor in a restaurant:

  • Kitchen (Redux Store): Where the food (state) is prepared and stored
  • Waitstaff (Container Components): Carry food from the kitchen to the tables, know the menu, take orders back to the kitchen
  • Dining Room (Presentational Components): Where customers interact with the food, tables arranged for optimal experience

Just as waitstaff don't cook the food and chefs don't interact directly with customers, containers handle Redux logic while presentational components handle UI without knowing about Redux.

Connected Components with TypeScript

When using TypeScript with Redux and React, you can add type safety to your connected components and hooks.

Typing connect()


import { connect, ConnectedProps } from 'react-redux';
import { RootState } from '../store';
import { toggleTodo } from '../actions';

// Define mapStateToProps with types
const mapStateToProps = (state: RootState) => ({
  todos: state.todos
});

// Define mapDispatchToProps
const mapDispatchToProps = {
  toggleTodo
};

// Generate type-safe props using ConnectedProps
const connector = connect(mapStateToProps, mapDispatchToProps);
type PropsFromRedux = ConnectedProps<typeof connector>;

// Component with props typing
interface TodoListProps extends PropsFromRedux {
  title: string; // Additional props not from Redux
}

function TodoList({ todos, toggleTodo, title }: TodoListProps) {
  return (
    <div>
      <h2>{title}</h2>
      <ul>
        {todos.map(todo => (
          <li
            key={todo.id}
            onClick={() => toggleTodo(todo.id)}
            style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}
          >
            {todo.text}
          </li>
        ))}
      </ul>
    </div>
  );
}

// Connect component to Redux
export default connector(TodoList);
        

Typing Hooks


import { useSelector, useDispatch } from 'react-redux';
import { RootState, AppDispatch } from '../store';
import { toggleTodo } from '../actions';

// Define typed hooks
export const useAppSelector = <T>(selector: (state: RootState) => T) => 
  useSelector<RootState, T>(selector);

export const useAppDispatch = () => useDispatch<AppDispatch>();

// Component with typed hooks
interface TodoListProps {
  title: string;
}

function TodoList({ title }: TodoListProps) {
  // Use typed selector
  const todos = useAppSelector(state => state.todos);
  
  // Use typed dispatch
  const dispatch = useAppDispatch();
  
  return (
    <div>
      <h2>{title}</h2>
      <ul>
        {todos.map(todo => (
          <li
            key={todo.id}
            onClick={() => dispatch(toggleTodo(todo.id))}
            style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}
          >
            {todo.text}
          </li>
        ))}
      </ul>
    </div>
  );
}

export default TodoList;
        

Benefits of type safety:

Testing Connected Components

Testing components connected to Redux requires some special considerations.

Testing Presentational Components

Presentational components are easy to test as they're just pure functions of their props.


// TodoList.test.js
import React from 'react';
import { render, fireEvent } from '@testing-library/react';
import TodoList from './TodoList';

test('renders todos and handles clicks', () => {
  const todos = [
    { id: 1, text: 'Test Todo', completed: false }
  ];
  const toggleTodo = jest.fn();
  
  const { getByText } = render(
    <TodoList todos={todos} toggleTodo={toggleTodo} />
  );
  
  const todoElement = getByText('Test Todo');
  expect(todoElement).toBeInTheDocument();
  
  fireEvent.click(todoElement);
  expect(toggleTodo).toHaveBeenCalledWith(1);
});
        

Testing Connected Components

For connected components, you have several options:

1. Mocking the Redux Store


// TodoList.test.js (for connected component)
import React from 'react';
import { render, fireEvent } from '@testing-library/react';
import { Provider } from 'react-redux';
import configureStore from 'redux-mock-store';
import ConnectedTodoList from './ConnectedTodoList';
import { toggleTodo } from '../actions';

const mockStore = configureStore([]);

test('connected component dispatches actions', () => {
  const initialState = {
    todos: [
      { id: 1, text: 'Test Todo', completed: false }
    ]
  };
  
  const store = mockStore(initialState);
  
  const { getByText } = render(
    <Provider store={store}>
      <ConnectedTodoList />
    </Provider>
  );
  
  fireEvent.click(getByText('Test Todo'));
  
  // Check that the correct action was dispatched
  expect(store.getActions()).toEqual([
    toggleTodo(1)
  ]);
});
        

2. Testing the Component in Isolation


// Export the unconnected component for testing
export function TodoList({ todos, toggleTodo }) {
  // Component implementation
}

// Connect and export the connected component
export default connect(
  mapStateToProps,
  mapDispatchToProps
)(TodoList);

// In your test file
import { TodoList } from './TodoList';  // Import the unconnected component

test('unconnected component', () => {
  // Test without Redux, just like a presentational component
});
        

3. Using a Real Redux Store


// Using a real Redux store for integration testing
import React from 'react';
import { render, fireEvent } from '@testing-library/react';
import { Provider } from 'react-redux';
import { createStore } from 'redux';
import rootReducer from '../reducers';
import ConnectedTodoList from './ConnectedTodoList';

test('connected component with real store', () => {
  const store = createStore(rootReducer, {
    todos: [
      { id: 1, text: 'Test Todo', completed: false }
    ]
  });
  
  const { getByText } = render(
    <Provider store={store}>
      <ConnectedTodoList />
    </Provider>
  );
  
  // Initial state
  expect(getByText('Test Todo')).toHaveStyle('text-decoration: none');
  
  // Click to toggle
  fireEvent.click(getByText('Test Todo'));
  
  // Check that the state was updated through the reducer
  expect(getByText('Test Todo')).toHaveStyle('text-decoration: line-through');
});
        

Practice Activities

Activity 1: Connecting a Todo App to Redux

Build a simple todo application with the following components:

  1. A form for adding new todos
  2. A list of todos that can be toggled between completed and incomplete
  3. Filter buttons to show all, completed, or active todos

Connect each component to Redux using both methods:

Activity 2: Container and Presentational Pattern

Refactor the todo app from Activity 1 to use the container/presentational pattern:

  1. Create presentational components that receive all data via props
  2. Create container components that connect to Redux and pass data to presentational components
  3. Ensure the presentational components have no Redux dependencies
  4. Write tests for both container and presentational components

Activity 3: Performance Optimization

Optimize the todo app for performance:

  1. Implement memoized selectors with reselect
  2. Use React.memo for appropriate components
  3. Apply shallowEqual for object comparisons in useSelector
  4. Batch updates when dispatching multiple actions
  5. Measure performance before and after optimizations

Summary

Further Resources