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.

Reactter provider some hooks to manage the state and side effects of the application, which are:

How it works

Hooks in Reactter are classes that extend ReactterHook and follow a special naming convention with the Use prefix. The ReactterHook 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 ReactterHook inherits from ReactterState this inheritance allows hooks to manage both state and lifecycle methods efficiently.

To manage these aspects, Hooks provide the following:

  • Methods inherited from ReactterState (Learn more here):

    • update : A method to notify changes after run a set of instructions.
    • refresh : A method to force to notify changes.
    • * bind : A method to bind an instance to it.
    • * unbind : A method to unbind an instance to it.
    • * dispose : A method to remove all listeners and free resources.

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 ReactterHook 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 ReactterHook {
5
// This line is REQUIRED!
6
final $ = ReactterHook.$register;
7
8
final controller = TextEditingController();
9
10
String _value = '';
11
String? get value => _value;
12
13
UseTextInput() {
14
UseEffect(() {
15
controller.addListener(() {
16
update(() => _value = controller.text);
17
});
18
19
return controller.dispose;
20
}, []);
21
}
22
}

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 ReactterHook class that allows you to update the state of the hook.

Using a custom 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 = Reactter.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 ReactterProvider<MyController>(
11
() => MyController(),
12
builder: (context, myController, child) {
13
return Scaffold(
14
appBar: AppBar(
15
title: const Text("Retrieve Text Input"),
16
),
17
body: Padding(
18
padding: const EdgeInsets.all(16),
19
child: Column(
20
crossAxisAlignment: CrossAxisAlignment.start,
21
children: [
22
ReactterConsumer<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 ReactterHook {
5
// This line is REQUIRED!
6
final $ = ReactterHook.$register;
7
8
final controller = TextEditingController();
9
10
String _value = '';
11
String? get value => _value;
12
13
UseTextInput() {
14
UseEffect(() {
15
controller.addListener(() {
16
update(() => _value = controller.text);
17
});
18
19
return controller.dispose;
20
}, []);
21
}
22
}
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 example above, the form captures first and last name inputs, combines them into a full name, and displays the result. MyController uses UseTextInput hook for capturing the first and last name. The fullName state is defined using UseCompute to compute the full name based on the values of firstNameInput and lastNameInput. This ensures the full name is automatically updated whenever either input changes.