Open jeiea opened 3 years ago
Could you share a full example?
Any information is needed?
Not really
I just need to find some time to work on it. It's a regression
Feel free to make a PR if it's urgent for you
I imagine you are extremely busy, but I'd also like to request a fix for this. <3
For anyone facing this issue and want a workaround:
Instead of re-using other hooks (since that causes this exception), I just re-implemented the other hooks I needed. For example, in my useIsConnected
hook to check for internet connectivity using DNS, I wanted to use useEffect
. Because of this issue, I just implemented it using ValueNotifier()..addListener()
, like useEffect
does.
I was hoping to make my custom class hook-aware by wrapping it into a hook, but stumbled into this issue too.
Any other way to have a viewmodel-like class with useReducer
inside it?
Here is a quick test for this case. I have tried looking into the code here https://github.com/rrousselGit/flutter_hooks/commit/ecbffc75c6280e9ec1992f5b9360c9b89b018869#diff-06572a96a58dc510037d5efa622f9bec8519bc1beab13c9f251e97e657a9d4ed but honestly this is beyond something I can understand within an hour to make a PR.
import 'package:flutter/cupertino.dart';
import 'package:flutter/src/widgets/framework.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:flutter_test/flutter_test.dart';
_TestHookState useTestHook() {
return use(_TestHook());
}
class _TestHook extends Hook<_TestHookState> {
@override
HookState<_TestHookState, Hook<_TestHookState>> createState() =>
_TestHookState();
}
class _TestHookState extends HookState<_TestHookState, _TestHook> {
@override
_TestHookState build(BuildContext context) {
useState(5);
return this;
}
}
void main() {
testWidgets('hooks can be used inside other hooks', (tester) async {
await tester.pumpWidget(
HookBuilder(builder: (_) {
useTestHook();
return Container();
}),
);
});
}
The workaround is to use your hooks within the custom useMyHook
:
_TestHookState useTestHook() {
final state = useState(5);
return use(_TestHook(state));
}
@rrousselGit Thanks, I will see if this can be applied in practice. Test case is, of course, very simplified.
Here is the more complicated code I have that fails. The idea is to abstract away all the logic into ViewModel, exposing only ValueNotifier with state to the Widget. And I would like to use hooks inside my ViewModel, at least in constructor, to make some async calls replacing the typical way data is fetched for the screen.
I know there are other ways to do that, like Riverpod, but current project I work on has only hooks and redux.
abstract class ViewModel<S> {
late ValueNotifier<S?> _stateNotifier;
ValueNotifier<S?> get state {
return _stateNotifier;
}
ViewModel(this._stateNotifier);
void dispose() {}
}
abstract class ViewModelFactory<S, V> {
V create(ValueNotifier<S?> stateNotifier);
}
ValueNotifier<S?> useViewModel<S, T extends ViewModel>(ViewModelFactory<S, T> factory) {
return use(_UseViewModelHook<S, T>(factory));
}
class _UseViewModelHook<S, T extends ViewModel> extends Hook<ValueNotifier<S?>> {
final ViewModelFactory<S, T> factory;
_UseViewModelHook(this.factory);
@override
HookState<ValueNotifier<S?>, Hook<ValueNotifier<S?>>> createState() => _UseViewModelState<S, T>();
}
class _UseViewModelState<S, T extends ViewModel>
extends HookState<ValueNotifier<S?>, _UseViewModelHook<S, T>> {
T? _viewModel = null;
final ValueNotifier<S?> valueNotifier = useValueNotifier<S?>(null);
@override
ValueNotifier<S?> build(BuildContext context) {
_viewModel = hook.factory.create(valueNotifier);
return valueNotifier;
}
@override
void dispose() {
_viewModel?.dispose();
}
}
===== Usage example
class UserProfileViewModelFactory extends ViewModelFactory<UserProfileState, UserProfileViewModel> {
final int? userId;
UserProfileViewModelFactory({required this.userId});
@override
UserProfileViewModel create(ValueNotifier<UserProfileState?> stateNotifier) =>
UserProfileViewModel(stateNotifier, userId: userId);
}
class UserProfileViewModel extends ViewModel<UserProfileState> {
final int? userId;
UserProfileViewModel(ValueNotifier<UserProfileState?> stateNotifier, {required this.userId})
: super(stateNotifier) {
state.value = UserProfileState(userId: userId);
useEffect(() {
Future.microtask(() async {
await _asyncInit();
});
});
}
_asyncInit() async {
state.value = state.value!.copyWith(test: "Test");
}
}
// Somewhere in the HookWidget
final state = useViewModel<UserProfileState, UserProfileViewModel>(
UserProfileViewModelFactory(userId: userId));
hey!
i like the workaround with useState
is there anything similar with useEffect
?
or is using useEffect
there generally discouraged?
Describe what scenario you think is uncovered by the existing examples/articles I want to use other hook in class hook.
Describe why existing examples/articles do not cover this case I thought I can use
useState
inHookState.build
, but it throws the following.I couldn't find the way to achieve the above.