Dispatching an action that does not alter state rerenders the widget, causing an infinite loop
Closed this issue · 5 comments
Very new to flutter.
I'm currently experiencing something which is weird in my book. I have a store that I'm connecting. Because of the lack of onMounted
or equivalent in react(coming from vue) I would like to trigger a side effect once when the widget renders. To do this I tried to dispatch something and then catch is as a middleware. But something is not working I feel like.
My widget is essentially this:
return StoreConnector<RedditState, RedditVM>(
builder: (context, redditVM) {
redditVM.pingSubreddit(subreddit);
// [...] omitted rest of component.
}
}
Looking at pingSubreddit
, you get this: store.dispatch(5)
. The reducer is checking the action type and performing logic accordingly. There is nothing that is supposed to happen when an integer is passed.
For some reason, this is rendering an infinite loop. I suppose that this package will always take a reducer and render the new components below it. Is this intended? I tried to replicate this using React's redux package by doing a dispatch during render. But it doesn't look like that forces an infinite loop.
Example:
import 'package:flutter/material.dart';
import 'package:redux/redux.dart';
enum Actions { increment }
int counterReducer(int state, dynamic action) {
return action == Actions.increment ? state + 1 : state;
}
void main() {
runApp(StoreProvider<int>(
child: MaterialApp(
home: Scaffold(
body: StoreConnector<int, VoidCallback>(
builder: (context, vm) {
print("render");
vm();
return const Text("data");
},
converter: (store) => () => store.dispatch("Foo")))),
store: store));
}
Restarted application in 927ms.
2064 I/flutter (17550): render
Application finished.
Exited (sigterm)
I believe you're looking for the onInit
callback function which can be passed to the StoreConnector
constructor.
https://pub.dev/documentation/flutter_redux/latest/flutter_redux/StoreConnector/onInit.html
If you dispatch an action inside the builder
function, it will send that action to the Redux reducer, which triggers a state change, which triggers a rebuild, which runs the builder
function, which dispatches again, which calls the recuer, produces a state changes, calls the builder... and you've found yourself in an infinite loop :)
Some sample code below (might be missing a paren somewhere -- github coding and didn't run it locally, but should get the idea across).
import 'package:flutter/material.dart';
import 'package:redux/redux.dart';
enum Actions { increment }
int counterReducer(int state, dynamic action) {
return action == Actions.increment ? state + 1 : state;
}
void main() {
runApp(StoreProvider<int>(
store: store
child: MaterialApp(
home: Scaffold(
body: StoreConnector<int, int>(
// Grab the current count from the store
converter: (store) => store.state,
// A function that runs once when the StoreConnector is inserted into the widget tree
onInit: (store) => store.dispatch(increment),
// Should be a pure function that returns a Widget tree and performs no side effects.
builder: (context, counter) {
return const Text("current count $counter");
},
),
),
);
}
Thanks @brianegan totally forgot onInit. It solves what i'm trying to do.
However, is it intended that when new state and old state is being reduced will still produce a rerender? Isn't that a bit wasteful?
Thanks! I'll have a look and reach out if I need more help. Much appreciated.