Closed bugeats closed 6 months ago
In my opinion, React Hooks are the best thing that's ever happened to React and reactive GUIs since functional components. It may be worth the time to take a look at what flutter_hooks is trying to do.
So far I'm getting the impression that Flutter/Dart is so heavily object-oriented that these sorts of simple functional approaches tend not to arise naturally.
@marcglasberg hi! Thank you for your work!
We used to use async_redux
package with flutter_hooks
For example:
Dispatcher useDispatcher() {
final context = useContext();
final storeProvider = StoreProvider.of<AppState>(context, 'dispatcher');
return storeProvider.dispatchAsync;
}
T useGlobalState<T>(
T Function(AppState s) converter, {
bool distinct = true,
}) {
final context = useContext();
final store = StoreProvider.of<AppState>(context, 'useGlobalState hook');
return use(_GlobalStateHook(
store: store,
converter: converter,
distinct: distinct,
));
}
As you see, it has been working via store
/direct calls of the dispatch
methods. In the 22.0.0 version the method StoreProvider.of
was removed. Is there any way to make async_redux
work with flutter_hooks
now?
Now I receive the error Cannot listen to inherited widgets inside HookState.initState. Use HookState.build instead
when I try to use useGlobalState/useDispatcher
which is ok, but as I understand I cannot dispatch/check state without inherited widget anymore?
@w3ggy
Note dispatchAsync
was renamed to dispatchAndWait
, and StoreProvider
now has a dispatchAndWait
static method.
So I think instead of:
StoreProvider.of<AppState>(context, 'dispatcher').dispatchAsync
You can use:
StoreProvider.dispatchAndWait<AppState>;
@w3ggy
Now, the reason I removed access to the store
through StoreProvider
is that accessing StoreProvider.state<St>
will rebuild the widget, while accessing StoreProvider.store<St>.state
will not. To compensate for the removal of StoreProvider.store<St>
I've created direct access to all methods:
extension BuildContextExtensionForProviderAndConnector<St> on BuildContext {
/// Get the state, without a StoreConnector.
/// Note: Widgets that use this method will rebuild whenever the state changes.
/// It's recommended that you define this extension in your own code:
/// `extension BuildContextExtension on BuildContext { AppState get state => getState<AppState>(); }`
/// This will allow you to write: `var state = context.state;`
St getState<St>() => StoreProvider.state<St>(this);
FutureOr<ActionStatus> dispatch(ReduxAction action, {bool notify = true}) => StoreProvider.dispatch(this, action, notify: notify);
Future<ActionStatus> dispatchAndWait(ReduxAction action, {bool notify = true}) => StoreProvider.dispatchAndWait(this, action, notify: notify);
ActionStatus dispatchSync(ReduxAction action, {bool notify = true}) => StoreProvider.dispatchSync(this, action, notify: notify);
/// ```
/// dispatch(MyAction());
/// if (context.isWaiting(MyAction)) { // Show a spinner }
/// ```
bool isWaiting(Object actionOrTypeOrList) => StoreProvider.isWaiting(this, actionOrTypeOrList);
/// ```
/// if (context.isFailed(MyAction)) { // Show an error message. }
/// ```
bool isFailed(Object actionOrTypeOrList) => StoreProvider.isFailed(this, actionOrTypeOrList);
/// ```
/// if (context.isFailed(SaveUserAction)) Text(context.exceptionFor(SaveUserAction)!.reason ?? '');
/// ```
UserException? exceptionFor(Object actionOrTypeOrList) => StoreProvider.exceptionFor(this, actionOrTypeOrList);
void clearExceptionFor(Object actionOrTypeOrList) => StoreProvider.clearExceptionFor(this, actionOrTypeOrList);
}
@w3ggy
Could you please show me the complete code for your _GlobalStateHook
?
@w3ggy
Note
dispatchAsync
was renamed todispatchAndWait
, andStoreProvider
now has adispatchAndWait
static method.So I think instead of:
StoreProvider.of<AppState>(context, 'dispatcher').dispatchAsync
You can use:
StoreProvider.dispatchAndWait<AppState>;
Yeah, this one works fine now. Thank you.
Dispatcher useDispatcher() {
final context = useContext();
return context.dispatchAndWait;
}
_GlobalStateHook:
Now, the reason I removed access to the store through StoreProvider is that accessing StoreProvider.state
will rebuild the widget, while accessing StoreProvider.store .state will not. To compensate for the removal of StoreProvider.store I've created direct access to all methods:
I got it. I am just wondering if there is any way to get access directly. It looks like after the 22.0 version there is no sense to use flutter_hooks
for state lookup anymore. But I already have legacy
code 😅 and I am trying to understand how to update async_redux without breaking changes for the old code base. In other words, I need the way to access to the state in the build
method (because flutter_hooks
builds hooks inside the build method). I tried to use something like this:
T useGlobalState<T>(
T Function(AppState s) converter, {
bool distinct = true,
}) {
final context = useContext();
final state = StoreProvider.state<AppState>(context);
return converter(state);
}
but it throws Cannot listen to inherited widgets inside HookState.initState. Use HookState.build instead
Thanks in advance!
Yes, I think you'd achieve the same result now by just writing context.state
, context.dispatch
, etc. But don't worry, I'll solve this for you.
Just one more question: Is GlobalState
an internal class from flutter_hooks
? If not could you please provide its code too? I need all code that's not provided by flutter_hooks
.
Yes, I think you'd achieve the same result now by just writing
context.state
,context.dispatch
, etc. But don't worry, I'll solve this for you.Just one more question: Is
GlobalState
an internal class fromflutter_hooks
? If not could you please provide its code too? I need all code that's not provided byflutter_hooks
.
Oh, sorry.
The GlobalState
is just an interface for the AppState
. You can replace GlobalState
with AppState
for local testing.
This one should work
@w3ggy @bugeats
I just published package https://pub.dev/packages/flutter_hooks_async_redux
You have to add the following dependencies to your pubspec.yaml
:
dependencies:
flutter_hooks: ^0.20.5 # or newer
async_redux: ^22.4.0 # or newer
flutter_hooks_async_redux: ^1.0.3 # or newer
And then, all features of Flutter Hooks and Async Redux are available for you.
useSelector
lets you select a part of the state and subscribe to updates.
You should give it a function that gets the sate and returns only the part of the state that the widget needs.
Example:
String username
= useSelector<AppState, String>((state) => state.username);
Note: If your state is called AppState
, you can define your own useAppState
hook,
like this:
T useAppState<T>(T Function(AppState state) converter, {bool distinct = true}) =>
useSelector<T, AppState>(converter, distinct: distinct);
This will simplify the use of the hook, like this:
String username = useAppState((state) => state.username);
@w3ggy
In your case, you should remove all your code and add this one:
T useGlobalState<T>(T Function(GlobalState state) converter, {bool distinct = true}) =>
useSelector<T, GlobalState>(converter, distinct: distinct);
Please replace your useDispatcher
to useDispatchAndWait
in your code. That's better, because there are other ways to dispatch, like useDispatch
and useDiaspatchSync
.
Important: Please do use this new package instead of fixing your old code, because then new Async Redux versions will continue to work in the future without you having to do any manual changes.
Also, could you please tell me if everything is working fine now? Thank you!
Thank you!
I added the flutter_hooks_async_redux
package and the useGlobalState
function to my project. But when I tried to use it, it throws: Cannot listen to inherited widgets inside HookState.initState. Use HookState.build instead
.
I prepared the small example. It doesn't work on my end when I call the dispatch
method inside useEffect
which is called during the build where we cannot use inherited widgets because under the hood useEffect
uses initState
inside the element where Inherited widgets are not accessible by design. 🤔
@w3ggy
Could you please try flutter_hooks_async_redux: ^1.0.3
and let me know?
@w3ggy Could you please try
flutter_hooks_async_redux: ^1.0.3
and let me know?
Dispatcher useDispatcher() {
final context = useContext();
return context.dispatchAndWait;
}
This one still doesn't work inside useEffect with the same error Cannot listen to inherited widgets inside HookState.initState. Use HookState.build instead
, but original useDispatch
does.
typedef Dispatcher = Future<void> Function(BaseAction);
Dispatcher useDispatcher() {
return useDispatchAndWait();
}
This one seems to be working well on initial testing. I will send this build on QA later and let you know if we found any other issues.
Thank you for your help! 😄
Yes, context.dispatchAndWait
is not supposed to work inside useEffect
. You should use the provided useDispatchAndWait()
. Glad it works. Let me know if you find any other problems. Thanks!
I'm a Flutter noob trying to get the nice state management stack that I'm accustomed to from react-redux. The combination of
useSelector
anduseDispatch
hooks is the cleanest and most concise state management strategy I've yet encountered, especially combined with reselect.The flutter_hooks package seems to provide similar react hooks functionality, but it appears that the dirty-checking model is totally different. For example
useState
returns aValueNotifier
wrapped value instead of just the actual value. I'm afraid it's currently beyond my Flutter experience to understand why that is and how to use it to get what I want.So far I have this:
And this:
It seems that @marcglasberg and @rrousselGit have collaborated successfully in the past. Maybe you guys can help me see this dream accomplished? Let me know if I should move this ticket to a different repo.