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

How to reuse other hooks in class hook? #215

Open
jeiea opened this issue Feb 4, 2021 · 11 comments
Open

How to reuse other hooks in class hook? #215

jeiea opened this issue Feb 4, 2021 · 11 comments
Assignees
Labels
bug Something isn't working

Comments

@jeiea
Copy link
Contributor

jeiea commented Feb 4, 2021

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 in HookState.build, but it throws the following.

Bad state: Type mismatch between hooks:
- previous hook: _Hook<T>
- new hook: _StateHook<S>

I couldn't find the way to achieve the above.

@rrousselGit
Copy link
Owner

Could you share a full example?

@jeiea
Copy link
Contributor Author

jeiea commented Feb 4, 2021

@jeiea
Copy link
Contributor Author

jeiea commented Feb 23, 2021

Any information is needed?

@rrousselGit
Copy link
Owner

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

@rrousselGit rrousselGit added bug Something isn't working and removed documentation needs triage labels Feb 23, 2021
@harkairt
Copy link

harkairt commented May 23, 2021

I imagine you are extremely busy, but I'd also like to request a fix for this. <3

@ben-xD
Copy link

ben-xD commented Dec 5, 2022

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.

@rrousselGit rrousselGit self-assigned this May 10, 2023
@AAverin
Copy link

AAverin commented May 24, 2023

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?

@AAverin
Copy link

AAverin commented May 24, 2023

Here is a quick test for this case. I have tried looking into the code here ecbffc7#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();
      }),
    );
  });
}

@rrousselGit
Copy link
Owner

The workaround is to use your hooks within the custom useMyHook:

_TestHookState useTestHook() {
  final state = useState(5);
  return use(_TestHook(state));
}

@AAverin
Copy link

AAverin commented May 24, 2023

@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));

@ksyro98
Copy link

ksyro98 commented Apr 10, 2024

hey!

i like the workaround with useState

is there anything similar with useEffect?
or is using useEffect there generally discouraged?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

6 participants