Closed alirashid18 closed 2 years ago
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
show your implementation with flutter_hooks
You cannot perform operation with the context API inside useEffect
in React either.
That doesn't work:
useEffect() => {
useContext(MyContext); // not a valid usage
}, []);
Although arguably, we could allow calling Provider's context.read
inside useEffect(() {...}, [])
@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.
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]);
It works, thanks @rrousselGit .
@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]);
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.
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);
}
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);
}
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)
@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? 🙂
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.
Oh okay, I thought calling async funcs in useEffect is considered bad in general.. 😅 Thanks @rrousselGit!
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);
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