This question is a duplicate of Flutter Redux and Hook. How to watch variable instance changing inside useEffect like react? but with a simplified and generic use case.
I’m using flutter_hooks
and flutter_redux
Here is my widget:
class Root extends HookWidget {
const Root({Key? key}) : super(key: key);
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
final store = StoreProvider.of<AppState>(context);
store.onChange.listen((AppState appState) {
print('store.onChange print: ${store.state.init}');
});
useEffect(() {
print('useEffect print: ${store.state.init}');
store.dispatch({'init': true});
}, [store.state.init]);
return Container();
}
}
Current ouput:
useEffect print: false
store.onChange print: true
Expected ouput:
useEffect print: false
store.onChange print: true
useEffect print: true
Why does useEffect
hook is not trigger when I update the state of the store while store.onChange
is clearly trigger ?
Edit
In react i can do something like this:
import React from "react";
import { useAppDispatch, useAppSelector } from "../../app/hooks";
import { RootState } from "../../app/store";
import Box from "@mui/material/Box";
import { set } from "../../app/globalSlice";
const MyComponent = React.memo(() => {
const dispatch = useAppDispatch();
const init = useAppSelector((state: RootState) => state.globalState.init);
React.useEffect(() => {
console.log("init state: " + init);
if (init === false) {
dispatch(set({ init: true }));
}
}, [init]);
return <Box />;
});
export default MyComponent;
output:
init state: false
init state: true
Notive that the useEffect
function has been call twice without having to use setState
.
Is there a way to do the same in flutter ?
2
Answers
I found the answer here: https://github.com/brianegan/flutter_redux/issues/244
You are changing the state of your store, but you are not calling setState() on the widget. And if you are using redux you should use
StoreConnector
widget inside your build method so as to properly update the state when store changes.Your store’s print log is called because
listen
on a stream subscription will always be called when you are updating your store. But useEffect will only be executed when Widget is being rebuilt.Currently you are dispatching a state update in useEffect() hook but nowhere setState() is called to update the current widget. And because of this build method is not called again so
useEffect
hook is not called again.The useEffect function is called every time a widget is being rebuilt but the widget is only being rebuilt once because changes in store doesn’t apply those changes by calling setState() on the widget.
So your widget is being built only one time so your logs show only one useEffect print statement but many store change statements.
To solve the issue, you should do the following:
Also, you should move the
store.state.onChange.listen
to the initState of the widget so as to prevent memory leaks because widget’s build method is called many times and you don’t want to create a new stream subscription every time you call the build method, so that you can properly dispose off the stream subscription after widget is unmounted in dispose.flutter_redux
provides aStoreConnector
widget which does what you want. It automatically rebuilds the widget without callingsetState((){})
explicitly. So you could do something like this:So where you use your Root widget your could wrap that widget with store connector.
For ex: