Closed cgestes closed 5 years ago
I'm not sure I understand what you want. You want to observe certain parts of the state, and then dispatch some actions when they change? You can do that using the StateObserver
(search for it in the documentation), but instead you should probably do that inside of the action that changed state.auth.profile
to start with.
Can you show me the code of the action that changed state.auth.profile
?
The code that changes the state of state.auth.profile:
class SetProfileEffect extends ReduxAction<AppState> {
final UserProfile profile;
SetProfileEffect(this.profile);
@override
AppState reduce() {
return state.copy(auth: state.auth.copy(profile: profile));
}
}
This action is dispatched by another authentication action that uses firebase_auth.
I have AuthState for auth management, and ContentState for the content of the app. ContentState should start running once the user is authenticated.
class AppState {
final AuthState auth;
final ContentState content;
}
I consider AuthState to be independant from ContentState.
StateObserver seems to do only part of the job, it doesn't filter state changes.
I started experimenting with the following solution:
import 'package:async_redux/async_redux.dart';
import 'package:collection/collection.dart';
class StoreReactor<SubState, State> {
var oldValue;
var subscription;
final Store<State> store;
final SubState Function(State) selector;
final void Function(Store<State>, SubState state) action;
StoreReactor(this.store, this.selector, this.action) {
assert(subscription == null);
oldValue = selector(store.state);
subscription = store.onChange.listen(_onChange);
}
_onChange(State state) {
var newValue = selector(state);
bool hasChanges =
DeepCollectionEquality.unordered().equals(oldValue, newValue) == false;
if (hasChanges == false) return;
oldValue = newValue;
action(store, newValue);
}
unsubscribe() {
subscription.cancel();
}
}
used like this:
var authReactor;
void setupAuthStateChanges(store) {
assert(authReactor == null);
var selector = (state) => state.auth.profile;
// we have an action in response
var action = (store, profile) {
if (profile != null) {
store.dispatch(InitializeContentAction());
} else {
store.dispatch(ResetContentAction());
}
};
authReactor = StoreReactor(store, selector, action);
}
In my opinion you are overcomplicating this. You could just do:
class SetProfileEffect extends ReduxAction<AppState> {
final UserProfile profile;
SetProfileEffect(this.profile);
@override
AppState reduce() {
if (profile != null) {
dispatch(InitializeContentAction());
} else {
dispatch(ResetContentAction());
}
return state.copy(auth: state.auth.copy(profile: profile));
}
}
That's right, I guess it's a matter of style. Anyway this is solved. Thank you. I can now react to state changes outside the widget hierarchy.
For reference, what I wanted to achieve: https://felangel.github.io/bloc/#/architecture?id=bloc-to-bloc-communication
I had to switch to bloc, cause the StoreReactor was racy, returning Future
Well, Redux has its own way of doing things, and I guess that observing state changes to dispatch actions, while totally possible, is not how it's supposed to be done, in principle. I guess your StoreReactor was trying to use Redux to do Bloc, which is bound to create problems, or at least to make it difficult for you to obtain help from other people who are more closely following Redux principles, and may not be able to give you advice when you depart from those principles (not that what you were trying to do was wrong at all, but just different). As I said, the usual approach would be to just dispatch InitializeContentAction/ResetContentAction from inside the reducer that changed your profile state. This would be simple and it works.
Also, Redux is all about being easy to reason about what's going on. Observing state to dispatch actions may reduce coupling, but it also makes it much more difficult to reason about what is going on. You are changing some state, and then unless you know all of your code you have no idea if some other actions are being dispatched because of that state change. On the contrary, if you just dispatch InitializeContentAction/ResetContentAction from inside the reducer that changed profile, then it's easy to see exactly what's going on.
That's true, I guess I always hope for my substore/subaction to be modular when I use redux, while in fact it should be thought as a global store for the application. I have always been puzzled with that when using Redux, but now that I discovered Bloc, it makes much more sense to me, and that's also how I would like to do React now :)
I really like the way you merged the reducer in the action :)
Btw I commented on another issue about the race if you are interested :)
Hi,
I would like to react to my authentication state and be able to emit events when it changes.
For now I do that outside of the store, I wonder if there is a simple way to subscribe to state changes and react to them.
I envision something that looks like this:
Seems pretty similar to StoreConnector but without the widget logic.
I wonder how you do it guys.