Skip to main content

Action selectors

In Redux, selectors are functions that take the Redux store state and return a specific piece of data.

Why selectors are useful:

  • They can compute derived data.
  • They hide the structure of the state, so components don't depend on how the state is organized.
  • Selectors can be cached (or memoized) to avoid unnecessary recalculations.

Example​

Suppose we have the following app state:

class AppState {
List<Item> items;
Item? selectedItem;
// ...
}

class Item {
String id;
// ...
}

And an action that selects an item by id:

class ViewItem extends AppAction {
final int id;
ViewItem(this.id);

AppState reduce() {
Item? item = state.items.firstWhereOrNull((item) => item.id == id);
if (item == null) throw UserException('Item not found');
return state.copy(viewedItem: item);
}
}

You would use it like this:

var item = Item('A');
dispatch(ViewItem(item));

Previously, in Action simplification, we saw how to create an abstract base action class AppAction to reduce boilerplate.

That same class can also be used to simplify state access:

abstract class AppAction extends ReduxAction<AppState> {

// Getter shortcuts
List<Item> get items => state.items;
Item get selectedItem => state.selectedItem;

// Selectors
Item? findById(int id) => items.firstWhereOrNull((item) => item.id == id);
Item? searchByText(String text) => items.firstWhereOrNull((item) => item.text.contains(text));
int get selectedIndex => items.indexOf(selectedItem);
}

Now your actions can read the store more directly:

class ViewItem extends AppAction {
final int id;
ViewItem(this.id);

AppState reduce() {
Item? item = findById(id); // Here!
if (item == null) throw UserException('Item not found');
return state.copy(viewedItem: item);
}
}

Instead of writing:

Item? item = state.items.firstWhereOrNull((item) => item.id == id);

You can simply write:

Item? item = findById(id);

In practice, your base action class may end up with many useful selectors that all your actions can use.

Creating a separate selector class​

Another option is to create a ActionSelect class and link it to your base action. This helps keep your base action cleaner.

First, define a ActionSelect class:

class ActionSelect {
final AppState state;
ActionSelect(this.state);

// Getter shortcuts
List<Item> get items => state.items;
Item get selectedItem => state.selectedItem;

// Selectors
Item? findById(int id) => items.firstWhereOrNull((item) => item.id == id);
Item? searchByText(String text) => items.firstWhereOrNull((item) => item.text.contains(text));
int get selectedIndex => items.indexOf(selectedItem);
}

Then link it in your base action:

abstract class AppAction extends ReduxAction<AppState> {
ActionSelect get select => ActionSelect(state);
}

Here's how you use it in your action:

class ViewItem extends AppAction {
final int id;
ViewItem(this.id);

AppState reduce() {
Item? item = select.findById(id); // Here!
if (item == null) throw UserException('Item not found');
return state.copy(viewed: item);
}
}

This also namespaces your selectors under select. Your IDE should be able to auto complete them for you!

Other usages​

Selectors are not limited to actions. You can also use widget selectors.