Skip to content

Hooks

Hooks are classes with the ability to use states and manage side effects. They are a fundamental concept in Reactter and are used to encapsulate logic that can be reused across the application.

API

Reactter provides the following hooks:

How it works

Hooks in Reactter are classes that extend RtHook and follow a special naming convention with the Use prefix. The RtHook class is responsible for binding the hook to other hooks and states, and managing the lifecycle of the hook.

Hooks in Reactter are essentially stateful entities because RtHook inherits from RtState this inheritance allows hooks to manage both state and lifecycle methods efficiently.

To manage these aspects, Hooks provide the following:

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.

Custom hook

Reactter provides a way to create custom hooks to encapsulate logic that can be reused across the application.

There are several advantages to using custom hooks:

  • Reusability: to use the same hook again and again, without the need to write it twice.
  • Clean Code: extracting part of code into a hook will provide a cleaner codebase.
  • Maintainability: easier to maintain. if you need to change the logic of the hook, you only need to change it once.

Creating a custom hook

To create a custom hook, you need to create a class that extends RtHook and follow the naming convention with the Use prefix.

Here’s an example of a custom hook that manages the state of a text input:

1
import 'package:reactter/reactter.dart';
2
import 'package:flutter/widgets.dart';
3
4
class UseTextInput extends RtHook {
5
// This line is REQUIRED!
6
final $ = RtHook.$register;
7
8
final controller = TextEditingController();
9
10
String _value = '';
11
String? get value => _value;
12
13
@override
14
initHook() {
15
UseEffect(() {
16
controller.addListener(() {
17
update(() => _value = controller.text);
18
});
19
20
return controller.dispose;
21
}, []);
22
}
23
}

As shown in the example above, we can utilize other hooks such as UseEffect to monitor changes in the text input’s controller and ensure it is disposed when the hook is destroyed.

The update method is used to set the internal _value to the current text in the controller, which keeps the state synchronized with the input. This methods is a part of the RtHook class that allows you to update the state of the hook.

Using the hook

You can then call that custom hook from anywhere in the code and get access to its shared logic:

1
import 'package:flutter_reactter/flutter_reactter.dart';
2
import 'use_text_input.dart';
3
4
class MyController {
5
final firstNameInput = UseTextInput();
6
final lastNameInput = UseTextInput();
7
8
late final fullName = Rt.lazyState(
9
() => UseCompute(
10
() {
11
final firstName = firstNameInput.value;
12
final lastName = lastNameInput.value;
13
14
return "$firstName $lastName";
15
},
16
[firstNameInput, lastNameInput],
17
),
18
this,
19
);
20
}
1
import 'package:flutter/material.dart';
2
import 'package:flutter_reactter/flutter_reactter.dart';
3
import 'my_controller.dart';
4
5
class MyCustomForm extends StatelessWidget {
6
const MyCustomForm({Key? key}) : super(key: key);
7
8
@override
9
Widget build(BuildContext context) {
10
return RtProvider<MyController>(
11
() => MyController(),
12
builder: (context, myController, child) {
13
return Scaffold(
14
appBar: AppBar(
15
title: const Text("Custom hook example"),
16
),
17
body: Padding(
18
padding: const EdgeInsets.all(16),
19
child: Column(
20
crossAxisAlignment: CrossAxisAlignment.start,
21
children: [
22
RtConsumer<MyController>(
23
listenStates: (myController) => [myController.fullName],
24
child: const Text(
25
"Full Name:",
26
style: TextStyle(fontWeight: FontWeight.bold),
27
),
28
builder: (context, myController, child) {
29
return Row(
30
children: [
31
child!,
32
const SizedBox(width: 4),
33
Text(myController.fullName.value),
34
],
35
);
36
},
37
),
38
const SizedBox(height: 8),
39
TextField(
40
decoration: InputDecoration(
41
hintText: "First Name",
42
),
43
controller: myController.firstNameInput.controller,
44
),
45
TextField(
46
decoration: InputDecoration(
47
hintText: "Last Name",
48
),
49
controller: myController.lastNameInput.controller,
50
),
51
],
52
),
53
),
54
);
55
},
56
);
57
}
58
}
1
import 'package:reactter/reactter.dart';
2
import 'package:flutter/widgets.dart';
3
4
class UseTextInput extends RtHook {
5
// This line is REQUIRED!
6
final $ = RtHook.$register;
7
8
final controller = TextEditingController();
9
10
String _value = '';
11
String? get value => _value;
12
13
@override
14
initHook() {
15
UseEffect(() {
16
controller.addListener(() {
17
update(() => _value = controller.text);
18
});
19
20
return controller.dispose;
21
}, []);
22
}
23
}
1
import 'package:flutter/material.dart';
2
import 'my_custom_form.dart';
3
4
void main() {
5
runApp(MyApp());
6
}
7
8
class MyApp extends StatelessWidget {
9
@override
10
Widget build(BuildContext context) {
11
return MaterialApp(
12
home: MyCustomForm(),
13
);
14
}
15
}

In the previous example, the form captures the input from the name and surname fields, combines them into a full name, and displays the result. The MyController component uses the UseTextInput hook (custom hook created previously) to manage the name and lastname inputs.

The fullName state is defined using UseCompute , which calculates the full name based on the values of firstNameInput and lastNameInput. This ensures that the full name updates automatically whenever either input changes.