Skip to content

Expanding the documentation #463

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

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Changes from 1 commit
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
82 changes: 49 additions & 33 deletions packages/flutter_hooks/lib/src/framework.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,27 @@ bool debugHotReloadHooksEnabled = true;

/// Registers a [Hook] and returns its value.
///
/// [use] must be called within the `build` method of either [HookWidget] or [StatefulHookWidget].
/// This function must be called inside the `build` method of a widget
/// that uses a [HookElement] as its build context.
/// All calls of [use] must be made outside of conditional checks and always in the same order.
///
/// See [Hook] for more explanations.
// 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 @@ -210,13 +217,17 @@ Calling them outside of build method leads to an unstable state and is therefore
/// HookState createState() => _MyHookState();
/// ```
///
/// The framework can call this method multiple times over the lifetime of a [HookWidget]. For example,
/// The framework can call this method multiple times over the lifetime of a [HookElement]. For example,
/// if the hook is used multiple times, a separate [HookState] must be created for each usage.
@protected
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 +288,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].
///
/// There is no guarantee that this method will be called after [markMayNeedRebuild]
/// was called.
/// Some situations where [shouldRebuild] will not be called:
/// If [shouldRebuild] returns `false` on all the hooks that called [markMayNeedRebuild],
/// [HookElement.build] will return a cached value instead of rebuilding each [Hook].
///
/// - [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;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I thought the original description was really good, but I saw that (1) a rebuild will happen anyway if "a previous hook's shouldRebuild returned true" and (2) the method returns true by default… and for some reason I totally missed that only the hooks that called markMayNeedRebuild are checked.

I also saw that the 3 bullet points technically didn't cover every possibility, since an InheritedWidget could also trigger an unconditional rebuild.


/// Mark the associated [HookWidget] as **potentially** needing to rebuild.
/// Mark the associated [context] 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 +376,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 +587,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 +605,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 All @@ -614,11 +624,17 @@ class _StatefulHookElement extends StatefulElement with HookElement {
_StatefulHookElement(StatefulHookWidget hooks) : super(hooks);
}

/// Obtains the [BuildContext] of the building [HookWidget].
/// Returns the [HookElement] that is currently running its `build()` method.
///
/// Throws an error if no hook widget is currently building.
///
/// Generally, Hook functions must be called unconditionally, in the same order.
/// That rule does not apply to `useContext()`, however, since instead of accessing a [Hook],
/// it merely returns the relevant [BuildContext].
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I can see an argument for calling useContext() unconditionally just for consistency, but since it can make things slightly less readable, I think it's nice to include the information here.

Copy link
Owner

Choose a reason for hiding this comment

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

I'd rather not tell people to do violate hooks rules with useContext specifically.
If we want to allow useContext to not respect those rules, then it shouldn't be named with the useX convention.

IMO diognistic tools could look for use* inside Widget.build and expect all of them to follow hook rules. I'd rather not have exceptions for this ; even if technically doable.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

IMO diognistic tools could look for use* inside Widget.build and expect all of them to follow hook rules.

That's a good point. I still think it'd be nice for developers who read these docs to have access to this info, but I agree that it shouldn't be recommended. I'll try to adjust the wording here accordingly.

BuildContext useContext() {
assert(
HookElement._currentHookElement != null,
'`useContext` can only be called from the build method of HookWidget',
'`useContext` can only be called while a Hook widget is building.',
);
return HookElement._currentHookElement!;
}
Expand Down