Skip to the content.

Redux Quick Reference


Creating a store

A store is made from a reducer function, which takes the current state, and returns a new state depending on the action it was given.

import { createStore } from 'redux'

// Reducer
function counter (state = { value: 0 }, action) {
  switch (action.type) {
  case 'INCREMENT':
    return { value: state.value + 1 }
  case 'DECREMENT':
    return { value: state.value - 1 }
  default:
    return state
  }
}

let store = createStore(counter)

// Optional - you can pass `initialState` as a second arg
let store = createStore(counter, { value: 0 })
↥ back to top

Using a store

Dispatch actions to change the store's state.

let store = createStore(counter)

// Dispatches an action; this changes the state
store.dispatch({ type: 'INCREMENT' })
store.dispatch({ type: 'DECREMENT' })

// Gets the current state
store.getState()

// Listens for changes
store.subscribe(() => { ... })
↥ back to top

Provider

The <Provider> component makes the store available in React components. It is used for connect().

import { Provider } from 'react-redux'

React.render(
  <Provider store={store}>
    <App />
  </Provider>, mountNode)
↥ back to top

Mapping state

import { connect } from 'react-redux'

// A functional React component
function App ({ message, onMessageClick }) {
  return (
    <div onClick={() => onMessageClick('hello')}>
      {message}
    </div>
  )
}

// Maps `state` to `props`:
// These will be added as props to the component.
function mapState (state) {
  return { message: state.message }
}

// Maps `dispatch` to `props`:
function mapDispatch (dispatch) {
  return {
    onMessageClick (message) {
      dispatch({ type: 'click', message })
    }
  }
}

// Connect them:
export default connect(mapState, mapDispatch)(App)
↥ back to top

Combining reducers

Combines multiple reducers into one reducer function.

const reducer = combineReducers({
  counter, user, store
})
↥ back to top

Signature

Middlewares are simply decorators for dispatch() to allows to take different kinds of actions, and to perform different tasks when receiving actions.

// noop middleware
const logger = store => dispatch => action { dispatch(action) }

const logger = store => {
  // This function runs on createStore().
  // It returns a decorator for dispatch().

  return dispatch => {
    // Runs on createStore(), too.
    // It returns a new dispatch() function

    return action => {
      // Runs on every dispatch()
    }
  }
}
↥ back to top

Applying middleware

const enhancer = applyMiddleware(logger, thunk, ...)

const store = createStore(reducer, {}, enhancer)
↥ back to top

Action creators

Action creators are functions that return action objects.

// Action type constants
const INCREMENT = 'INCREMENT'
const DECREMENT = 'DECREMENT'

// Action creators
const increment = () => ({ type: INCREMENT })
const decrement = () => ({ type: DECREMENT })
const incrementBy = (amount) => ({ type: 'INCREMENT_BY', payload: amount })

// Dispatching via action creators
store.dispatch(increment())
store.dispatch(incrementBy(5))
↥ back to top

Thunks (async actions)

Thunks let you dispatch functions instead of plain objects for async logic.

import { createStore, applyMiddleware } from 'redux'
import thunk from 'redux-thunk'

const store = createStore(reducer, applyMiddleware(thunk))

// A thunk action creator
function fetchUser(id) {
  return async (dispatch) => {
    dispatch({ type: 'FETCH_USER_REQUEST' })
    try {
      const res = await fetch(`/api/users/${id}`)
      const data = await res.json()
      dispatch({ type: 'FETCH_USER_SUCCESS', payload: data })
    } catch (err) {
      dispatch({ type: 'FETCH_USER_FAILURE', error: err.message })
    }
  }
}

store.dispatch(fetchUser(1))
↥ back to top

React Hooks

useSelector reads from the store; useDispatch returns the dispatch function.

import { useSelector, useDispatch } from 'react-redux'

function Counter() {
  const count = useSelector((state) => state.counter.value)
  const dispatch = useDispatch()

  return (
    <div>
      <span>{count}</span>
      <button onClick={() => dispatch({ type: 'INCREMENT' })}>+</button>
      <button onClick={() => dispatch({ type: 'DECREMENT' })}>-</button>
    </div>
  )
}
↥ back to top

Redux Toolkit – configureStore

configureStore wraps createStore with good defaults (DevTools, thunk middleware).

import { configureStore } from '@reduxjs/toolkit'
import counterReducer from './counterSlice'

const store = configureStore({
  reducer: {
    counter: counterReducer,
  },
})

export default store
↥ back to top

Redux Toolkit – createSlice

createSlice generates action creators and action types automatically.

import { createSlice } from '@reduxjs/toolkit'

const counterSlice = createSlice({
  name: 'counter',
  initialState: { value: 0 },
  reducers: {
    increment(state) {
      state.value += 1          // Immer allows direct mutation
    },
    decrement(state) {
      state.value -= 1
    },
    incrementByAmount(state, action) {
      state.value += action.payload
    },
  },
})

export const { increment, decrement, incrementByAmount } = counterSlice.actions
export default counterSlice.reducer
↥ back to top

Redux Toolkit – createAsyncThunk

Handles async lifecycles (pending, fulfilled, rejected) automatically.

import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'

export const fetchUser = createAsyncThunk('users/fetchById', async (userId) => {
  const res = await fetch(`/api/users/${userId}`)
  return res.json()
})

const usersSlice = createSlice({
  name: 'users',
  initialState: { data: null, status: 'idle', error: null },
  reducers: {},
  extraReducers: (builder) => {
    builder
      .addCase(fetchUser.pending,   (state) => { state.status = 'loading' })
      .addCase(fetchUser.fulfilled, (state, action) => {
        state.status = 'succeeded'
        state.data = action.payload
      })
      .addCase(fetchUser.rejected,  (state, action) => {
        state.status = 'failed'
        state.error = action.error.message
      })
  },
})

export default usersSlice.reducer
↥ back to top

Selectors

Selectors are functions that extract and derive data from state.

// Basic selector
const selectCount = (state) => state.counter.value

// Derived selector
const selectDoubleCount = (state) => state.counter.value * 2

// Usage with useSelector
const count = useSelector(selectCount)
↥ back to top

Memoized selectors with Reselect

createSelector memoizes expensive derived state computations.

import { createSelector } from 'reselect'  // or '@reduxjs/toolkit'

const selectItems = (state) => state.cart.items
const selectTax   = (state) => state.cart.taxRate

const selectTotal = createSelector(
  [selectItems, selectTax],
  (items, taxRate) => {
    const subtotal = items.reduce((sum, item) => sum + item.price, 0)
    return subtotal * (1 + taxRate)
  }
)

// Only recomputes when `items` or `taxRate` change
const total = useSelector(selectTotal)
↥ back to top

Redux DevTools

Enable browser DevTools support (built into configureStore by default).

import { createStore } from 'redux'

// Legacy createStore – enable DevTools manually
const store = createStore(
  reducer,
  window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
)

// Redux Toolkit – DevTools are enabled automatically
import { configureStore } from '@reduxjs/toolkit'
const store = configureStore({ reducer })
↥ back to top