Skip to content

Binding State to Dependency

A state( ReactterState like Signal or any Hooks ) can be bound to the dependency, allowing the state to be manipulated directly from the dependency and to notify its listeners about any changes. Additionally, it ensures that the state is automatically disposed of when the dependency is no longer needed.

By integrating state directly within dependencies, you benefit from cleaner and more maintainable code. The automatic handling by Reactter means less boilerplate and fewer errors related to manual state management, leading to a more efficient development process. This approach simplifies the synchronization between state and its associated dependency, enhancing the overall responsiveness and reliability of your application.

Automatic binding

For that happened automatically, the state must be declared as a property or within constructor of the dependency. When this is done, Reactter automatically takes care of binding the state to the dependency, ensuring seamless state management and reactivity, e.g.:

count_controller.dart
1
import "package:reactter/reactter.dart";
2
3
class CountController {
4
// State declared as property
5
final uCount = UseState(0);
6
7
CountController() {
8
// State declared within constructor
9
UseEffect(() {
10
print("Count: ${uCount.value}");
11
}, [uCount]);
12
}
13
}

In the example above, the uCount state is declared as a property of the CountController class and the UseEffect hook is used within the constructor to react to changes in the uCount state, printing its value whenever it changes. This automatically binds the uCount state and UseEffect hook to the CountController instance, demonstrates how Reactter handles the binding and reactivity seamlessly.

main.dart
1
import "./count_controller.dart";
2
3
void main() {
4
final controller = Reactter.create(() => CountController(10));
5
controller.uCount.value += 2; // Count: 12
6
Reactter.delete<CountController>();
7
controller.uCount.value += 3; // Error: "Can't update when it's been disposed"
8
}

In the example above, the uCount state is accessed after creating the CountController instance, demonstrating that the state is bound to the dependency and can be manipulated directly from the instance. When the CountController instance is deleted, the state is automatically disposed of, ensuring that resources are released efficiently.

Lazy state binding

When a state is declared lazily, it is not automatically bound to the dependency. In such cases, you can use the Reactter.lazyState method to bind the state to the dependency, e.g.:

count_controller.dart
1
import "package:reactter/reactter.dart";
2
3
class CountController {
4
final int initialCount;
5
6
late final uCount = Reactter.lazyState(
7
() => UseState(this.initialCount),
8
this,
9
);
10
11
CountController([this.initialCount = 0]) {
12
UseEffect(() {
13
print("Count: ${uCount.value}");
14
}, [uCount]);
15
}
16
}

In the example above, the uCount state is declared lazily using the late keyword. To bind the state to the CountController instance, the Reactter.lazyState method is used, passing the state creation function and the dependency instance as arguments. This ensures that when uCount is accessed, it will be automatically bound to the CountController instance, e.g.:

main.dart
1
import "./count_controller.dart";
2
3
void main() {
4
final controller = Reactter.create(() => CountController(10));
5
controller.uCount.value += 2; // Count: 12
6
Reactter.delete<CountController>();
7
controller.uCount.value += 3; // Error: "Can't update when it's been disposed"
8
}

In the example above, the uCount state is accessed after creating the CountController instance, demonstrating that the state is bound to the dependency and can be manipulated directly from the instance. When the CountController instance is deleted, the state is automatically disposed of, ensuring that resources are released efficiently.

Manual binding

While automatic binding simplifies state management, there may be scenarios where you need to manually bind the state to a dependency. Manual binding provides greater control over how and when the state is associated with the dependency.

To manually bind a state to a dependency, you need to explicitly link the state within the dependency using the bind method of the state, e.g.:

count_controller.dart
1
class CountController {
2
late final uCount = UseState(this.initialCount);
3
4
final int initialCount;
5
6
CountController([this.initialCount = 0]) {
7
count.bind(this);
8
}
9
}

In the example above, the uCount state is declared lazily using the late keyword. To manually bind the state to the CountController instance, the bind method is called within the constructor, passing the dependency instance as an argument. This ensures that the uCount state is associated with the CountController instance, e.g.:

main.dart
1
import "./count_controller.dart";
2
3
void main() {
4
final controller = Reactter.create(() => CountController(10));
5
controller.uCount.value += 2; // Count: 12
6
Reactter.delete<CountController>();
7
controller.uCount.value += 3; // Error: "Can't update when it's been disposed"
8
}

In the example above, the uCount state is accessed after creating the CountController instance, demonstrating that the state is bound to the dependency and can be manipulated directly from the instance. When the CountController instance is deleted, the state is automatically disposed of, ensuring that resources are released efficiently.