select()
Listen to only a specific part of a provider's state to avoid unnecessary widget rebuilds.
What is it?
select() is a Riverpod API that allows a widget or provider to watch only a derived value instead of the entire state.
Normally, ref.watch(provider) rebuilds whenever the provider's state changes. With select(), Riverpod compares only the selected value and rebuilds only if that value changes.
This is one of the most effective optimization techniques for reducing rebuilds in large applications.
Why does it exist?
Many state objects contain multiple fields.
For example:
class User {
final String name;
final String email;
final String avatarUrl;
final bool isPremium;
}
If a widget only displays the user's name, it shouldn't rebuild when the email or avatar changes.
Without select(), every change to the User object causes the widget to rebuild.
select() solves this by subscribing only to the property that the widget actually uses.
Syntax
Watching a single property
final name = ref.watch(
userProvider.select((user) => user.name),
);
Explanation:
select()transforms the provider's state into the value you want to observe.- The widget rebuilds only when
user.namechanges.
Watching a boolean
final isPremium = ref.watch(
userProvider.select((user) => user.isPremium),
);
Explanation:
- Only changes to
isPremiumtrigger a rebuild. - Other changes to the
Userobject are ignored.
Using multiple selections
final name = ref.watch(
userProvider.select((user) => user.name),
);
final theme = ref.watch(
settingsProvider.select((settings) => settings.themeMode),
);
Explanation:
- Each
select()independently tracks its selected value. - The widget rebuilds only when one of the selected values changes.
Mental Model
Without select():
User
├── Name
├── Email
├── Avatar
└── Premium
│
▼
Entire widget rebuilds
With select():
User
├── Name ◄──────── Watching
├── Email
├── Avatar
└── Premium
Only rebuild when Name changes
Think of select() as subscribing to a single field instead of the entire object.
Examples
Simple Example
class UserName extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final name = ref.watch(
userProvider.select((user) => user.name),
);
return Text(name);
}
}
Explanation:
- The widget ignores changes to every property except
name.
Real-World Example
class PremiumBadge extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final isPremium = ref.watch(
userProvider.select((user) => user.isPremium),
);
if (!isPremium) {
return const SizedBox.shrink();
}
return const Icon(Icons.star);
}
}
Explanation:
- The badge rebuilds only when the premium status changes.
- Changes to the user's name or profile picture do not affect this widget.
When to Use
Use select() when:
- Watching large state objects.
- Displaying only one property.
- Optimizing frequently rebuilt widgets.
- Building large lists.
- Creating dashboards.
- Working with immutable models.
When NOT to Use
Avoid select() when:
- The widget needs the entire object.
- The provider exposes only a single value.
- The optimization adds unnecessary complexity.
Don't use select() everywhere by default. Use it where it provides a measurable benefit.
Best Practices
- Keep selected values simple.
- Select immutable values.
- Use
select()for frequently changing state. - Prefer multiple small providers over deeply nested selections.
- Avoid expensive computations inside the selector.
Common Mistakes
Watching the whole object
Wrong:
final user = ref.watch(userProvider);
return Text(user.name);
Explanation:
- Any change to the
Userobject rebuilds the widget.
Correct:
final name = ref.watch(
userProvider.select((user) => user.name),
);
return Text(name);
Explanation:
- Only changes to
nametrigger a rebuild.
Selecting computed values with expensive logic
Wrong:
final value = ref.watch(
userProvider.select(
(user) => heavyCalculation(user),
),
);
Explanation:
- The selector executes whenever Riverpod evaluates the provider.
- Expensive work should not be performed here.
Correct:
final score = ref.watch(
userProvider.select((user) => user.score),
);
Explanation:
- Keep selectors lightweight.
- Move heavy computations into dedicated providers.
Selecting mutable collections
Wrong:
final items = ref.watch(
cartProvider.select((cart) => cart.items),
);
Explanation:
- Mutating the same list instance may prevent Riverpod from detecting changes correctly.
Correct:
final itemCount = ref.watch(
cartProvider.select((cart) => cart.items.length),
);
Explanation:
- Prefer immutable collections or select simple immutable values.
Related APIs
ref.watch()- Provider
- NotifierProvider
- AsyncNotifierProvider
- ConsumerWidget
- WidgetRef
- Provider Families
- Minimizing Rebuilds
Summary
select() allows widgets to listen to only the part of a provider's state that they actually use. By selecting specific properties instead of entire objects, you can significantly reduce unnecessary rebuilds and improve the performance of large Riverpod applications.