Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expanding the documentation #463

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 37 additions & 28 deletions packages/flutter_hooks/lib/src/framework.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,19 @@ bool debugHotReloadHooksEnabled = true;
// ignore: deprecated_member_use, deprecated_member_use_from_same_package
R use<R>(Hook<R> hook) => Hook.use(hook);

/// [Hook] is similar to a [StatelessWidget], but is not associated
/// to an [Element].
/// Allows a [Widget] to create and access its own mutable data
/// without implementing a [State].
Comment on lines -20 to +21
Copy link
Contributor Author

@nate-thegrate nate-thegrate Mar 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought that "not associated to an Element" might lead to confusion, because much like a StatefulWidget, Hook has a createState() method, and the object returned has access to the context.

///
/// A [Hook] is typically the equivalent of [State] for [StatefulWidget],
/// with the notable difference that a [HookWidget] can have more than one [Hook].
/// A [Hook] is created within the [HookState.build] method of a [HookWidget] and the creation
/// must be made unconditionally, always in the same order.
/// Whereas [Widget]s store the immutable configuration for UI components,
/// [Hook]s store immutable configuration for any type of object.
/// The [HookState] of a [Hook] is analogous to the [State] of a [StatefulWidget],
/// and a single [HookWidget] can use more than one [Hook].
///
/// Hooks can be used by replacing `extends StatelessWidget` with `extends HookWidget`,
/// or by replacing `Builder()` with `HookBuilder()`.
///
/// Hook functions must be called unconditionally during the `build()` method,
/// and always in the same order.
Comment on lines -23 to +32
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A [Hook] is typically the equivalent of [State]

This makes sense, since you can go by the philosophy of "rather than making a State, make a Hook instead".
However, in my mind, the equivalent of State would be HookState rather than Hook.
My hope is that the adjusted wording creates a good intuition while avoiding this caveat.


A [Hook] is created within the [HookState.build] method of a [HookWidget]

I believe this sentence was a typo: a HookState is created by a Hook, not the other way around.

///
/// ### Good:
/// ```
Expand Down Expand Up @@ -216,7 +222,11 @@ Calling them outside of build method leads to an unstable state and is therefore
HookState<R, Hook<R>> createState();
}

/// The logic and internal state for a [HookWidget]
/// The logic and internal state of a [Hook].
///
/// This class is similar to a [State], but instead of building a [Widget]
/// subtree, the [build] method can return a value of any type, as specified
/// by the "result" type argument `R`.
abstract class HookState<R, T extends Hook<R>> with Diagnosticable {
/// Equivalent of [State.context] for [HookState]
@protected
Expand Down Expand Up @@ -277,22 +287,19 @@ abstract class HookState<R, T extends Hook<R>> with Diagnosticable {

/// Called before a [build] triggered by [markMayNeedRebuild].
///
/// If [shouldRebuild] returns `false` on all the hooks that called [markMayNeedRebuild]
/// then this aborts the rebuild of the associated [HookWidget].
/// If [shouldRebuild] returns `false` on all the hooks that called [markMayNeedRebuild],
/// [HookElement.build] will return a cached value instead of rebuilding each [Hook].
///
/// There is no guarantee that this method will be called after [markMayNeedRebuild]
/// was called.
/// Some situations where [shouldRebuild] will not be called:
///
/// - [setState] was called
/// - a previous hook's [shouldRebuild] returned `true`
/// - the associated [HookWidget] changed.
/// This method is not evaluated if a previous Hook called [markMayNeedRebuild]
/// and its [shouldRebuild] method returned `true`.
/// Additionally, if [setState], [didUpdateHook], or [HookElement.didChangeDependencies] is called,
/// the build is unconditional and the `shouldRebuild()` call is skipped.
bool shouldRebuild() => true;

/// Mark the associated [HookWidget] as **potentially** needing to rebuild.
///
/// As opposed to [setState], the rebuild is optional and can be cancelled right
/// before `build` is called, by having [shouldRebuild] return false.
/// before [build] is called, by having [shouldRebuild] return false.
void markMayNeedRebuild() {
if (_element!._isOptionalRebuild != false) {
_element!
Expand Down Expand Up @@ -368,7 +375,10 @@ extension on HookElement {
}
}

/// An [Element] that uses a [HookWidget] as its configuration.
/// An [Element] that manages [Hook]s by storing the associated [HookState]s in a [LinkedList].
///
/// [use] and [useContext] can only be called during this element's [build] method.
/// The `_buildCache` enables the behavior described in [HookState.shouldRebuild].
mixin HookElement on ComponentElement {
static HookElement? _currentHookElement;

Expand Down Expand Up @@ -576,14 +586,12 @@ Type mismatch between hooks:
}
}

/// A [Widget] that can use a [Hook].
/// A [Widget] that can use [Hook]s.
Comment on lines -579 to +589
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My hope was to tweak the wording here, so it's more explicit that you can use multiple Hooks.

///
/// Its usage is very similar to [StatelessWidget].
/// [HookWidget] does not have any life cycle and only implements
/// the [build] method.
/// Similar to [StatelessWidget], a `HookWidget` is created by extending this class.
///
/// The difference is that it can use a [Hook], which allows a
/// [HookWidget] to store mutable data without implementing a [State].
/// The [HookWidget.build] method can [use] Hook functions, allowing this widget
/// to store mutable data without implementing a [State].
abstract class HookWidget extends StatelessWidget {
/// Initializes [key] for subclasses.
const HookWidget({Key? key}) : super(key: key);
Expand All @@ -596,12 +604,13 @@ class _StatelessHookElement extends StatelessElement with HookElement {
_StatelessHookElement(HookWidget hooks) : super(hooks);
}

/// A [StatefulWidget] that can use a [Hook].
/// A [StatefulWidget] that can use [Hook]s.
///
/// Its usage is very similar to that of [StatefulWidget], but uses hooks inside [State.build].
/// Similar to [StatefulWidget], this widget creates a [State] which
/// can store mutable data.
///
/// The difference is that it can use a [Hook], which allows a
/// [HookWidget] to store mutable data without implementing a [State].
/// The difference is that [Hook] functions can be called from within [State.build],
/// similar to [HookWidget.build].
abstract class StatefulHookWidget extends StatefulWidget {
/// Initializes [key] for subclasses.
const StatefulHookWidget({Key? key}) : super(key: key);
Expand Down
Loading