Skip to content

UseReducer

UseReducer is a hook that manages state using reducer function. An alternative to UseState .

Syntax

UseReducer<T>(
T reducer(T state, RtAction action),
T initialValue,
{ String? debugLabel },
);

UseReducer accepts these parameters:

  • reducer : A function that takes the current state and an action, and returns a new state.
  • initialState: Initial value of T type that it will hold.
  • debugLabel: (optional) A label to identify the hook in the DevTools extension.

Properties & Methods

UseReducer provides the following properties and methods:

  • value: A getter that allows to read its state.
  • dispatch : A method that allows to dispatch an action to update the state.
    • Syntax:
      1
      void dispatch(RtAction action);
Properties and methods inherited from RtState
  • debugLabel: A string that represents the label of the state object for debugging purposes.
  • debugInfo: A map that contains debug information about the state object.
  • update : Executes a callback function and notify its observers that the state has changed. When it is invoked, it emits two lifecycle events to signal the state transition:
    • Lifecycle.willUpdate is emitted first, indicating the impending update.
    • Lifecycle.didUpdate is emitted once the update process is complete.
  • notify : Forces the state to notify its observers. Unlike update , it emits only the Lifecycle.didUpdate event, as it doesn’t involve any preparatory steps before the notification.
  • bind : Establishes a connection between the state and a specific instance. This connection allows the instance to reactively update based on changes to the state. By binding the state, the instance becomes aware of changes to the state and can appropriately reflect those changes in its behavior.
  • unbind : Releases the connection between the state and the instance. When unbinding, the instance will no longer receive updates from the state. This can be useful when an instance is no longer actively using the state or when it needs to detach from the state temporarily or permanently.
  • dispose : Is responsible for cleaning up the state and any associated observers or resources. Disposing of the state ensures that it is properly released and no longer consumes memory or processing resources unnecessarily.

Usage

Declaration

UseReducer can be initialized using the constructor class:

29 collapsed lines
1
import 'package:reactter/reactter.dart';
2
3
class User {
4
final String name;
5
final int age;
6
7
const User({required this.name, required this.age});
8
9
@override
10
String toString() => "User(name: $name, age: $age)";
11
}
12
13
class UserAction extends ReactterAction {
14
UserAction.incrementedAge() : super(type: 'incremented_age', payload: null);
15
UserAction.changedName(String name)
16
: super(type: 'changed_name', payload: name);
17
}
18
19
User reducer(User state, ReactterAction action) {
20
switch (action.type) {
21
case "incremented_age":
22
return User(state.name, state.age + 1);
23
case "changed_name":
24
return User(action.payload as String, state.age);
25
default:
26
return state;
27
}
28
}
29
30
final uUser = UseReducer<User>(
31
reducer,
32
const User(
33
name: "John Doe",
34
age: 17,
35
),
36
);
8 collapsed lines
37
38
void main() {
39
print("${uUser.value}") // User(name: "John Doe", age: 17)
40
uUser.dispatch(UserAction.incrementedAge());
41
print("${uUser.value}"); // User(name: "John Doe", age: 18)
42
uUser.dispatch(UserAction.changedName("Jane Doe"));
43
print("${uUser.value}"); // User(name: "Jane Done", age: 18)
44
}

Writting the action

The action is a class that inherits from the RtAction class.

This class contains the action type and payload that will be dispatched to the reducer method.

12 collapsed lines
1
import 'package:reactter/reactter.dart';
2
3
class User {
4
final String name;
5
final int age;
6
7
const User({required this.name, required this.age});
8
9
@override
10
String toString() => "User(name: $name, age: $age)";
11
}
12
13
class UserAction extends ReactterAction {
14
UserAction.incrementedAge() : super(type: 'incremented_age', payload: null);
15
UserAction.changedName(String name)
16
: super(type: 'changed_name', payload: name);
17
}
27 collapsed lines
18
19
User reducer(User state, ReactterAction action) {
20
switch (action.type) {
21
case "incremented_age":
22
return User(state.name, state.age + 1);
23
case "changed_name":
24
return User(action.payload as String, state.age);
25
default:
26
return state;
27
}
28
}
29
30
final uUser = UseReducer<User>(
31
reducer,
32
const User(
33
name: "John Doe",
34
age: 17,
35
),
36
);
37
38
void main() {
39
print("${uUser.value}") // User(name: "John Doe", age: 17)
40
uUser.dispatch(UserAction.incrementedAge());
41
print("${uUser.value}"); // User(name: "John Doe", age: 18)
42
uUser.dispatch(UserAction.changedName("Jane Doe"));
43
print("${uUser.value}"); // User(name: "Jane Done", age: 18)
44
}

Writting the reducer method

The reducer method is a function that takes the current state and an action, and returns a new state. This method is responsible for updating the state based on the action dispatched. The state is immutable, so it should return a new state object.

18 collapsed lines
1
import 'package:reactter/reactter.dart';
2
3
class User {
4
final String name;
5
final int age;
6
7
const User({required this.name, required this.age});
8
9
@override
10
String toString() => "User(name: $name, age: $age)";
11
}
12
13
class UserAction extends ReactterAction {
14
UserAction.incrementedAge() : super(type: 'incremented_age', payload: null);
15
UserAction.changedName(String name)
16
: super(type: 'changed_name', payload: name);
17
}
18
19
User reducer(User state, ReactterAction action) {
20
switch (action.type) {
21
case "incremented_age":
22
return User(state.name, state.age + 1);
23
case "changed_name":
24
return User(action.payload as String, state.age);
25
default:
26
return state;
27
}
28
}
16 collapsed lines
29
30
final uUser = UseReducer<User>(
31
reducer,
32
const User(
33
name: "John Doe",
34
age: 17,
35
),
36
);
37
38
void main() {
39
print("${uUser.value}") // User(name: "John Doe", age: 17)
40
uUser.dispatch(UserAction.incrementedAge());
41
print("${uUser.value}"); // User(name: "John Doe", age: 18)
42
uUser.dispatch(UserAction.changedName("Jane Doe"));
43
print("${uUser.value}"); // User(name: "Jane Done", age: 18)
44
}

Dispatching an action and reading the state

The dispatch method is responsible for dispatching an action to the reducer method. After dispatching an action, you can read the state using the value property.

37 collapsed lines
1
import 'package:reactter/reactter.dart';
2
3
class User {
4
final String name;
5
final int age;
6
7
const User({required this.name, required this.age});
8
9
@override
10
String toString() => "User(name: $name, age: $age)";
11
}
12
13
class UserAction extends ReactterAction {
14
UserAction.incrementedAge() : super(type: 'incremented_age', payload: null);
15
UserAction.changedName(String name)
16
: super(type: 'changed_name', payload: name);
17
}
18
19
User reducer(User state, ReactterAction action) {
20
switch (action.type) {
21
case "incremented_age":
22
return User(state.name, state.age + 1);
23
case "changed_name":
24
return User(action.payload as String, state.age);
25
default:
26
return state;
27
}
28
}
29
30
final uUser = UseReducer<User>(
31
reducer,
32
const User(
33
name: "John Doe",
34
age: 17,
35
),
36
);
37
38
void main() {
39
print("${uUser.value}") // User(name: "John Doe", age: 17)
40
uUser.dispatch(UserAction.incrementedAge());
41
print("${uUser.value}"); // User(name: "John Doe", age: 18)
42
uUser.dispatch(UserAction.changedName("Jane Doe"));
43
print("${uUser.value}"); // User(name: "Jane Done", age: 18)
44
}

Using the action callable

The actions can be created as a callable class, extending from RtActionCallable e.g.:

1
class IncrementedEgeUserAction extends RtActionCallable<User, void> {
2
const IncrementedEgeUserAction()
3
: super(type: 'incremented_ege', payload: null);
4
5
@override
6
User call(User state) => User(name: state.name, age: state.age + 1);
7
}
8
9
class ChangedNameUserAction extends RtActionCallable<User, String> {
10
final String name;
11
12
const ChangedNameUserAction(this.name)
13
: super(type: 'changed_name', payload: name);
14
15
@override
16
User call(User state) => User(name: payload, age: state.age);
17
}

RtActionCallable has a call method that returns the new state based on the state and payload.

The action callable can be applied in reducer method:

1
User reducer(User state, RtAction action) {
2
if (action is RtActionCallable) return action(state);
3
return state;
4
}

And dispatched as:

1
userState.dispatch(IncrementedEgeUserAction());
2
userState.dispatch(ChangedNameUserAction('Jane Doe'));

Updating the state

Use update method to notify changes after run a set of instructions:

uState.update((value) {
uState.value = "New value";
});

Use notify method to force to notify changes.

userState.notify();

Listening to changes

When value has changed, the UseReducer will emit the following events(learn about it here):

  • Lifecycle.willUpdate event is triggered before the change in value or update method have been invoked.
  • Lifecycle.didUpdate event is triggered after the change in value or after update or notify methods have been invoked.

Example of listening to changes:

1
Rt.on(
2
uUser,
3
Lifecycle.didUpdate,
4
(_, state) => print("State value has changed to: ${state.value}"),
5
);