# Learn how to use Redux

> Learn the current Redux approach with Redux Toolkit: create a slice, configure the store, connect React, and read or update shared state.

Author: [Flavio Copes](https://flaviocopes.com/about/) | Published: 2018-01-28 | Updated: 2026-07-18 | Topics: [React](https://flaviocopes.com/tags/react/) | Canonical: https://flaviocopes.com/redux/

Redux is a library for managing shared application state. Today, the official way to write Redux code is [Redux Toolkit](https://redux-toolkit.js.org/).

You might use Redux when state is shared by many parts of an application, changes in several ways, and has become hard to manage with local component state alone.

Do not add Redux automatically to every React application. React state and context are often enough for smaller applications.

## The Redux data flow

Redux has a one-way data flow:

1. The store holds the current state.
2. A component dispatches an action describing what happened.
3. A reducer calculates the next state.
4. Subscribed components render with the new state.

Redux Toolkit keeps these ideas but removes most of the boilerplate that older Redux examples required.

## Install Redux Toolkit

For a React application, install Redux Toolkit and React Redux:

```bash
npm install @reduxjs/toolkit react-redux
```

`@reduxjs/toolkit` contains the recommended Redux APIs. `react-redux` connects the store to React.

## Create a slice

A slice groups the state, reducer logic, and generated action creators for one feature.

Create `features/todos/todosSlice.js`:

```js
import { createSlice } from '@reduxjs/toolkit'

const initialState = {
  items: [],
}

const todosSlice = createSlice({
  name: 'todos',
  initialState,
  reducers: {
    todoAdded(state, action) {
      state.items.push({
        id: crypto.randomUUID(),
        text: action.payload,
        completed: false,
      })
    },
    todoToggled(state, action) {
      const todo = state.items.find((item) => item.id === action.payload)

      if (todo) {
        todo.completed = !todo.completed
      }
    },
    todoRemoved(state, action) {
      state.items = state.items.filter((item) => item.id !== action.payload)
    },
  },
})

export const { todoAdded, todoToggled, todoRemoved } = todosSlice.actions
export default todosSlice.reducer
```

The reducer code appears to mutate `state`. This is safe inside `createSlice`: Redux Toolkit uses Immer to produce an immutable next state from those changes.

Do not mutate Redux state outside a Redux Toolkit reducer.

## Configure the store

Create `app/store.js`:

```js
import { configureStore } from '@reduxjs/toolkit'
import todosReducer from '../features/todos/todosSlice.js'

export const store = configureStore({
  reducer: {
    todos: todosReducer,
  },
})
```

`configureStore()` combines the reducers, adds useful development checks, and enables the Redux DevTools Extension.

The resulting state has this shape:

```js
{
  todos: {
    items: []
  }
}
```

## Make the store available to React

Wrap the application with React Redux's `Provider`:

```jsx
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import { Provider } from 'react-redux'
import App from './App.jsx'
import { store } from './app/store.js'

createRoot(document.getElementById('root')).render(
  <StrictMode>
    <Provider store={store}>
      <App />
    </Provider>
  </StrictMode>,
)
```

Components inside `Provider` can now read from the store and dispatch actions.

## Read state with useSelector

`useSelector()` receives the entire Redux state and returns the part a component needs:

```jsx
import { useSelector } from 'react-redux'

export default function TodoList() {
  const todos = useSelector((state) => state.todos.items)

  return (
    <ul>
      {todos.map((todo) => (
        <li key={todo.id}>{todo.text}</li>
      ))}
    </ul>
  )
}
```

The component renders again when the selected value changes.

Selectors can be moved into the slice file when you reuse them:

```js
export const selectTodos = (state) => state.todos.items
```

Then use `useSelector(selectTodos)`.

## Dispatch actions with useDispatch

`useDispatch()` returns the store's `dispatch` function:

```jsx
import { useState } from 'react'
import { useDispatch } from 'react-redux'
import { todoAdded } from './features/todos/todosSlice.js'

export default function AddTodo() {
  const [text, setText] = useState('')
  const dispatch = useDispatch()

  function handleSubmit(event) {
    event.preventDefault()

    const value = text.trim()
    if (!value) return

    dispatch(todoAdded(value))
    setText('')
  }

  return (
    <form onSubmit={handleSubmit}>
      <input
        value={text}
        onChange={(event) => setText(event.target.value)}
      />
      <button>Add todo</button>
    </form>
  )
}
```

`todoAdded(value)` creates an action such as:

```js
{
  type: 'todos/todoAdded',
  payload: 'Buy milk'
}
```

The store sends that action to the slice reducer, which calculates the next state.

## Load asynchronous data

Reducers must be synchronous and must not perform side effects. Put network requests outside reducers.

Redux Toolkit includes `createAsyncThunk()` for request lifecycles, and [RTK Query](https://redux-toolkit.js.org/rtk-query/overview) for fetching and caching server data.

Before adding a thunk, consider whether RTK Query or your framework's data-loading API already solves the problem.

## What happened to createStore?

Older tutorials use `createStore()`, hand-written action type constants, `combineReducers()`, and large `switch` statements.

Those APIs still explain Redux's foundations, but the Redux maintainers recommend Redux Toolkit for new code. `configureStore()` replaces the typical `createStore()` setup, and `createSlice()` generates action creators and reducer cases together.

The official [Redux Toolkit quick start](https://redux.js.org/tutorials/quick-start) and [Why Redux Toolkit is Redux Today](https://redux.js.org/introduction/why-rtk-is-redux-today) explain the current approach in more detail.
