Reducer
Each application defines at least one reducer. In our todo app example the reducer must be able to handle the 4 actions we defined in the previous section.

The reducer has two main responsibility and an optional one:
- Define the initial state for the todo app in the form of
initialState
in iOS andgetEmptyState
in android (check the sample below). - Define the
reduce
function that takes both theaction
and the currentstate
as parameters and returns a new state. - (Optional) The
stateKey
for thereducer
state. We expand on this parameter later.
For each action, the reducer
returns a new copy of the todo state. Let's take a look at the reducer code.
struct TodoReducer: Reducer {
var initialState = TodoList(todos: [])
func reduce(action: Action, state: TodoList) -> TodoList? {
if let action = action as? AddTodo {
var newState = state
newState.todos = newState.todos + [TodoItem(title: action.text, isCompleted: false)]
return newState
}
if let action = action as? RemoveTodo {
var newState = state
newState.todos.remove(at: action.index)
return newState
}
if let action = action as? MoveTodo {
var newState = state
let element = newState.todos.remove(at: action.from)
newState.todos.insert(element, at: action.to)
return newState
}
if let action = action as? ToggleTodo {
var newState = state
var post = newState.todos[action.index]
post.isCompleted = !post.isCompleted
newState.todos[action.index] = post
return newState
}
return nil
}
}
class TodoReducer : Reducer<TodoList>() {
override fun reduce(oldState: TodoList, action: Action<*>): TodoList? {
return when(action) {
is AddTodo -> {
val newTodoItem = TodoItem(title = action.text, isCompleted = false)
oldState.copy(todos = oldState.todos + newTodoItem)
}
is RemoveTodo -> {
val todoToRemove = oldState.todos[action.index]
oldState.copy(todos = oldState.todos - todoToRemove)
}
is MoveTodo -> {
val mutableTodos = oldState.todos.toMutableList()
val itemToMove = mutableTodos.removeAt(action.from)
mutableTodos.add(action.to, itemToMove)
oldState.copy(todos = mutableTodos.toList())
}
is ToggleTodo -> {
val mutableTodos = oldState.todos.toMutableList()
val itemToToggle = mutableTodos.removeAt(action.index)
val toggledItem = itemToToggle.copy(isCompleted = !itemToToggle.isCompleted)
mutableTodos.add(action.index, toggledItem)
oldState.copy(todos = mutableTodos.toList())
}
else -> null
}
}
override fun getEmptyState(): TodoList = TodoList()
}
class TodoReducer extends Reducer<TodoList> {
@Nullable
@Override
public TodoList reduce(@NonNull TodoList oldState, @NonNull Action<?> action) {
switch (action.getActionType()) {
case ADD_TODO: {
String title = action.getData();
TodoItem newItem = new TodoItem(title, false);
List<TodoItem> todoItems = oldState.getTodos();
todoItems.add(newItem);
return new TodoList(todoItems);
}
case REMOVE_TODO: {
int index = action.getData();
List<TodoItem> todoItems = oldState.getTodos();
todoItems.remove(index);
return new TodoList(todoItems);
}
case MOVE_TODO: {
final Pair<Integer, Integer> data = action.getData();
int from = data.getFirst();
int to = data.getSecond();
List<TodoItem> todoItems = oldState.getTodos();
TodoItem itemToMove = todoItems.remove(from);
todoItems.add(to, itemToMove);
return new TodoList(todoItems);
}
case TOGGLE_TODO: {
int index = action.getData();
List<TodoItem> todoItems = oldState.getTodos();
TodoItem itemToToggle = todoItems.remove(index);
todoItems.add(index, new TodoItem(itemToToggle.getTitle(), !itemToToggle.isCompleted()));
return new TodoList(todoItems);
}
}
return null;
}
@NonNull
@Override
public TodoList getEmptyState() {
return new TodoList();
}
}
For each of the actions, the reducer does the following:
- Checks for the action type
- Create a new state based on that action
Let's study how the reducer
handles the AddTodo
action:
if let action = action as? AddTodo {
var newState = state
newState.todos = newState.todos + [TodoItem(title: action.text, isCompleted: false)]
return newState
}
is AddTodo -> {
val newTodoItem = TodoItem(title = action.text, isCompleted = false)
oldState.copy(todos = oldState.todos + newTodoItem)
}
case ADD_TODO: {
String title = action.getData();
TodoItem newItem = new TodoItem(title, false);
List<TodoItem> todoItems = oldState.getTodos();
todoItems.add(newItem);
return new TodoList(todoItems);
}
The reducer first checks if the action is an AddTodo
action. It then creates a new TodoItem
and returns a new state that has that item appended to the list of todos.
Finally, if the reducer is unaware of this action, it returns nil/null
from the reduce
function. By returning nil/null
, it sends a signal to the Store that it did not change the state. This, in turn, causes the store to hold off on sending a notification to the listeners.
Note on more advanced usages
If your application contains multiple screens that are not logically or functionally tied; for example, in your todo app you might have a settings screen. In this case, instead of having a single reducer with two responsibilities we can instead combine to sub reducers and have a more modular reducer structure. Head to applications with multiple states to read more.
iOS - Block Reducer
In iOS you can also create a reducer inline by using BlockReducer
. For example, we can rewrite the reducer above as follows:
let todoReducer = BlockReducer(state: TodoList(todos: [])) { action, state in
if let action = action as? AddTodo {
var newState = state
newState.todos = newState.todos + [TodoItem(title: action.text, isCompleted: false)]
return newState
}
if let action = action as? RemoveTodo {
var newState = state
newState.todos.remove(at: action.index)
return newState
}
// Handle other actions
return nil
}
The BlockReducer
init method takes three parameters:
- The
initialState
for the reducer. This corresponds to theinitialState
var in theReducer
protocol. - The reduce block, this block will receive the
action
and thestate
and return the new state. This block corresponds to thereduce
function in theReducer
protocol - (Optional) The
stateKey
for thereducer
state. We expand on this parameter later.
Changing the reducer state key
If your app state consists of multiple reducers, the Store
state is held as a dictionary. By default, the state key equals the state type name (class name or struct name) and value equal the sub state itself.
In some rare advanced situations, you can change the state key by passing a different when creating the reducer.
Check applications with multiple states for more info about this field. For most cases, this field will be left unset.
What's Next
We have actions
, states
and reducers
we next cover the store that glues them together.
Related Topics
Other Suas architecture components
Listener
Also, check:
List of example applications built with Suas
Updated about 6 years ago