Skip to content

Redux - Actions

Actions are the only way your application can send data to the Redux store. They are plain JavaScript objects that represent an intention to change the state. Think of them as events describing something that happened in the application.

The core requirement for an action object is a type property. This property typically holds a string that uniquely identifies the kind of action being performed (e.g., ‘user/login’, ‘todos/add’, ‘counter/increment’).

// Example Action Objects
{
type: 'todos/addTodo',
payload: { id: 1, text: 'Learn Redux', completed: false }
}
{
type: 'counter/increment'
}
{
type: 'user/updateProfile',
payload: { name: 'Jane Doe', email: 'jane.doe@example.com' }
}

The type acts as an identifier for reducers to know how to handle the action. While type is mandatory, the rest of the action object’s structure is up to you. However, a common convention is to put any data associated with the action inside a payload field.

Manually creating these action objects everywhere in your code can be repetitive and error-prone. Action Creators are functions that create and return action objects. This encapsulates the action creation logic, promotes consistency, and makes actions easier to test and maintain.

Simple Action Creator Example:

// Manually defining action type constant
const ADD_TODO = 'todos/addTodo';
// Action creator function
function addTodo(text) {
return {
type: ADD_TODO,
payload: {
id: Date.now(), // Simple way to generate a unique ID
text: text,
completed: false
}
};
}
// Usage:
const action = addTodo('Learn Action Creators');
// action is now:
// { type: 'todos/addTodo', payload: { id: 1678886... , text: 'Learn Action Creators', completed: false } }

Creating an action or using an action creator doesn’t change the state by itself. You need to dispatch the action to the Redux store using the store.dispatch() method. Only then will the store run the action through its reducers to calculate the new state.

// Assuming 'store' is your configured Redux store
// Dispatching an action object directly
store.dispatch({ type: 'counter/increment' });
// Dispatching an action using an action creator
const newTodoText = 'Dispatch actions';
store.dispatch(addTodo(newTodoText));

In applications using UI libraries like React, you typically don’t call store.dispatch() directly from components. Instead, libraries like react-redux provide helper functions (like the useDispatch hook) to get access to the dispatch function within your components.

Redux Toolkit significantly reduces the boilerplate associated with actions and action creators. Its createSlice function automatically generates action types and action creators based on the reducer functions you define.

import { createSlice } from '@reduxjs/toolkit';
const counterSlice = createSlice({
name: 'counter', // Used to generate action type prefix ('counter/increment')
initialState: { value: 0 },
reducers: {
// Reducer function for the 'increment' action
increment(state) {
state.value += 1; // Immer allows direct mutation here!
},
// Reducer function for the 'decrement' action
decrement(state) {
state.value -= 1;
},
// Reducer function that accepts a payload
incrementByAmount(state, action) {
// action.payload contains the value passed to the action creator
state.value += action.payload;
}
}
});
// Action creators are generated automatically!
export const { increment, decrement, incrementByAmount } = counterSlice.actions;
// The reducer function is also generated
export default counterSlice.reducer;
// Usage:
store.dispatch(increment()); // Dispatches { type: 'counter/increment' }
store.dispatch(decrement()); // Dispatches { type: 'counter/decrement' }
store.dispatch(incrementByAmount(5)); // Dispatches { type: 'counter/incrementByAmount', payload: 5 }

As you can see, createSlice handles creating the action type strings (e.g., ‘counter/increment’) and the corresponding action creator functions (e.g., increment()), making the process much more concise and less error-prone.