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

useEffect hook not working properly. #153

Closed
alirashid18 opened this issue Jul 12, 2020 · 15 comments
Closed

useEffect hook not working properly. #153

alirashid18 opened this issue Jul 12, 2020 · 15 comments
Labels
enhancement New feature or request

Comments

@alirashid18
Copy link

In React, useEffect hook works after first build. Bu in flutter_hooks, useEffect works before first render. That's why, make an operation with context object on useEffect(() { //operation using context }, []) makes following error:
Cannot listen to inherited widgets inside HookState.initState. Use HookState.build instead.

REACT HOOKS WORKING EXAMPLE
Screen Shot 2020-07-12 at 13 58 40

@gabrielvictorjs
Copy link

In React, useEffect hook works after first build. Bu in flutter_hooks, useEffect works before first render. That's why, make an operation with context object on useEffect(() { //operation using context }, []) makes following error:
Cannot listen to inherited widgets inside HookState.initState. Use HookState.build instead.

REACT HOOKS WORKING EXAMPLE
Screen Shot 2020-07-12 at 13 58 40

show your implementation with flutter_hooks

@rrousselGit
Copy link
Owner

You cannot perform operation with the context API inside useEffect in React either.

That doesn't work:

useEffect() => {
  useContext(MyContext); // not a valid usage
}, []);

@rrousselGit
Copy link
Owner

Although arguably, we could allow calling Provider's context.read inside useEffect(() {...}, [])

@rrousselGit rrousselGit added the enhancement New feature or request label Jul 26, 2020
@alirashid18
Copy link
Author

@rrousselGit For example.
I have config.json file in my assets folder. I need to read its data using DefaultAssetBundle. When I read data using loadString method (DefaultAssetBundle.of(context).loadString('assets/config.json')) it shows me an error.
After this error, I copied useEffect hook source code and changed it initHook method as follows:
void initHook() {
super.initHook();
WidgetsBinding.instance.addPostFrameCallback((_) { scheduleEffect(); });
}

Then I used this hook and it worked as expectedly.

Screen Shot 2020-07-28 at 00 13 51

Screen Shot 2020-07-28 at 00 12 42

@rrousselGit
Copy link
Owner

Whatever.of is equivalent to useContext(Whatever).
It is normal that this code is not allowed.

Instead do:

final bundle = DefaultAssetBundle.of(context);

useEffect(() {
  bundle.loadString('...').then(...);
}, [bundle]);

@alirashid18
Copy link
Author

It works, thanks @rrousselGit .

@danielmahon
Copy link

@rrousselGit What's the proper way to use a StateProvider inside useEffect without Future.microtask? Or is it required?

I get this error without it:

[VERBOSE-2:ui_dart_state.cc(171)] Unhandled Exception: setState() or markNeedsBuild() called during build.
This UncontrolledProviderScope widget cannot be marked as needing to build because the framework is already in the process of building widgets.  A widget can be marked as needing to be built during the build phase only if one of its ancestors is currently building. This exception is allowed because the framework builds parent widgets before children, which means a dirty descendant will always be built. Otherwise, the framework might not visit this widget during this build phase.
The widget on which setState() or markNeedsBuild() was called was:
  UncontrolledProviderScope
@freezed
abstract class AppState with _$AppState {
  const factory AppState({
    @Default(false) bool hasOnboarded,
    Widget fab,
    @Default([]) List<DepartmentModel> favorites,
  }) = _AppState;
}

final stateProvider = StateProvider((_) => const AppState());

final stateController = useProvider(stateProvider);

useEffect(() {
  // Future.microtask(() {
  stateController.state = stateController.state.copyWith(
    fab: FloatingActionButton(
      backgroundColor: theme.accentColor,
      onPressed: () {},
      child: const Icon(Icons.share),
    ),
  );
  // });
  return () {
    // Future.microtask(() {
    stateController.state = stateController.state.copyWith(fab: null);
    // });
  };
}, [stateController]);

@rrousselGit
Copy link
Owner

rrousselGit commented Sep 10, 2020

Or is it required

It is required. The exception was implemented on purpose to prevent modifying a provider inside build without the microtask because that is dangerous.

@danielmahon
Copy link

I figured, thanks. Pretty trivial to add my own useAsyncEffect. Loving riverpod + hooks by the way! Do you think it's worth revisiting adding a built-in useAsyncEffect to flutter_hooks?

void useAsyncEffect(
  FutureOr<dynamic> Function() effect,
  FutureOr<dynamic> Function() cleanup, [
  List<Object> keys,
]) {
  useEffect(() {
    Future.microtask(effect);
    return () {
      if (cleanup != null) {
        Future.microtask(cleanup);
      }
    };
  }, keys);
}

@fdietze
Copy link

fdietze commented Jun 23, 2021

This is my version of useAsyncEffect:
(It tries to keep the signature similar to useEffect)

void useAsyncEffect(Future<Dispose?> Function() effect, [List<Object?>? keys]) {
  useEffect(() {
    final disposeFuture = Future.microtask(effect);
    return () => disposeFuture.then((dispose) => dispose?.call());
  }, keys);
}

@rrousselGit
Copy link
Owner

Closing since everything is working as expected.

I don't plan on adding a useAsyncEffect for now. You shouldn't need such a thing and it's likely a sign of code smell (such as modifying a state in the wrong place)

@najibghadri
Copy link

@rrousselGit Can you elaborate please why is this a sign of code smell?

I have many places in my code where a firs-time useEffect calls an async function.. some might use the context too. Why is this bad? 🙂

@rrousselGit
Copy link
Owner

Because if your problem is "can't call setState", the solution isn't to silence the error by using a hacky workaround of delaying the update by a frame

You should refactor your code such that the update is performed before all listeners of the object were built.

@najibghadri
Copy link

najibghadri commented Dec 19, 2021

Oh okay, I thought calling async funcs in useEffect is considered bad in general.. 😅 Thanks @rrousselGit!

@shtse8
Copy link

shtse8 commented Nov 8, 2023

Because if your problem is "can't call setState", the solution isn't to silence the error by using a hacky workaround of delaying the update by a frame

You should refactor your code such that the update is performed before all listeners of the object were built.

Thanks for your explanation.
Does it mean there is nothing wrong with useAsyncEffect?
What if I want to do something in async while there is any changes on values?
For example, in splash screen, there is a login process (async) when entering the page. after logging in successfully, user will be routed to another page. What is the correct implementation without useAsyncEffect?

Here is the code:

nextPage() {
      Navigator.of(context).push(
        MaterialPageRoute(
          maintainState: false,
          builder: (context) => LobbyPage(),
        ),
      );
    }

    // final acconutNotifier = ref.watch(accountProvider.notifier);
    final loginFuture = useMemoized(() => () async {
          controller.forward(); // animation
          final acconutNotifier = ref.read(accountProvider.notifier);
          await acconutNotifier.login();
          nextPage();
        }());
    useFuture(loginFuture);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

7 participants