rrousselGit / flutter_hooks

React hooks for Flutter. Hooks are a new kind of object that manages a Widget life-cycles. They are used to increase code sharing between widgets and as a complete replacement for StatefulWidget.
MIT License
3.07k stars 175 forks source link

How to watch redux store change inside useEffect? #350

Closed Lenny4 closed 1 year ago

Lenny4 commented 1 year ago

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

    useEffect(() {
      print('useEffect print: ${store.state.init}');
      if(store.state.init == false) {
        store.dispatch({'init': true});
      }
    }, [store.state.init]);

    return Container();
  }
}

Current ouput:

useEffect print: false

Expected ouput:

useEffect print: false
useEffect print: true

Why does useEffect hook is not trigger when I update the state of the store ?

In react i can do something like this:

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 in React and 1 time in flutter.

I have opened an issue on flutter_redux here, the maintainer told me to use another third party package to handle this case, but none I found on internet seems to work.

Do you plan to implement a feature which will allow to use useEffect to watch store changes ?

rrousselGit commented 1 year ago

Who knows. Is your widget even rebuilding due to your dispatch?

After all, your JS vs Dart snippets do different things:

        store.dispatch({'init': true});

vs

      dispatch(set({ init: true }));

In the JS variant, you dispatch a set action which likely updates the store state with the input parameter.
But in the Dart code, you don't have this set action. Either you have no action at all, or you're dispatching an init action. In which case you'd need a reducer to handle the init action and do some state change.

It's unclear to me that this is handled correctly, so my assumption is that useEffect works but your reducer/store doesn't.

Lenny4 commented 1 year ago

Hi @rrousselGit, thank for your reply.

It's unclear to me that this is handled correctly, so my assumption is that useEffect works but your reducer/store doesn't.

If I modify my dart code like this:

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();
  }
}

I got this output:

useEffect print: false
store.onChange print: true

As you can see my store.onChange is trigger. So my reducer/store seems to work correctly.

Do you agree ?

rrousselGit commented 1 year ago

Possibly, or possibly not.

You'd have to put a print in Root.build before the useEffect to see if the widget actually rebuilds. Because it could be that your change triggers a print but the widget does not rebuild.

Lenny4 commented 1 year ago

Is there a way to trigger the rebuild function without having to change the state of the widget ?

As you suggest the widget is not rebuild by the store.dispatch({'init': true});.

What is you recommended way to achieve what I described ?

rrousselGit commented 1 year ago

That's up to the redux package to do so here. For example StoreProvider.of<AppState>(context) could cause the dependent widget to listen to the app state.