Combine reducers in Flutter

Issue

I am using Redux with Flutter with this library. If the solution is to change to redux dart library then I will change it.

This is the code I have for the store, I have been taking parts from different tutorials

@immutable
class AppState {
  final SignUpState signUpState;
  final LoginState loginState;

  AppState({
    @required this.signUpState,
    @required this.loginState,
  });
  AppState copyWith({
    SignUpState signUpState,
    LoginState loginState,
  }) {
    return AppState(
      signUpState: signUpState ?? this.signUpState,
      loginState: loginState ?? this.loginState,
    );
  }
}

AppState appReducer(AppState state, action) {  
  return AppState(
    signUpState: signUpReducer(state.signUpState, action),
    // loginState: loginReducer(state.loginState, action),
  );
}

class Redux {
  static Store<AppState> _store;

  static Store<AppState> get store {
    if (_store == null) {
      throw Exception("store is not initialized");
    } else {
      return _store;
    }
  }

  static Future<void> init() async {
    final signUpStateInitial = SignUpState.initial();
    final loginStateInitial = LoginState.initial();

    _store = Store<AppState>(
      appReducer,
      middleware: [thunkMiddleware, new LoggingMiddleware.printer()],
      initialState: AppState(
          signUpState: signUpStateInitial,
          loginState: loginStateInitial,
      ),
    );
  }
}

I am testing first with signUpState, which currently works when I only use that one, the moment I add another reducer and uncomment the line

// loginState: loginReducer(state.loginState, action),

on trying to dispatch a signUp related Action that does work with the above line commented I get Uncaught (in promise) Error: Expected a value of type 'SetLoginStateAction', but got one of type 'SetSignUpStateAction'

I thought this part of the code was combining the reducers:

AppState appReducer(AppState state, action) {  
      return AppState(
        signUpState: signUpReducer(state.signUpState, action),
        loginState: loginReducer(state.loginState, action),
      );
    }

How would I do it then?

Edit: I changed the code like this now, but still problem persists

@immutable
// Define your State
class AppState {
  final SignUpState signUpState;
  final LoginState loginState;

  AppState(this.signUpState, this.loginState);
}

AppState appReducer(AppState state, action) => new AppState(
  signUpReducer(state.signUpState, action),
  loginReducer(state.loginState, action),
);

class Redux {
  static Store<AppState> _store;

  static Store<AppState> get store {    
    if (_store == null) {
      throw Exception("store is not initialized");
    } else {       
      return _store;
    }
  }

  static Future<void> init() async {
    // print(1);
    final signUpStateInitial = SignUpState.initial();
    // print(2);
    final loginStateInitial = LoginState.initial();
    // print(3);

    _store = Store<AppState>(
      appReducer,
      middleware: [thunkMiddleware, new LoggingMiddleware.printer()],
      initialState: AppState(
          signUpStateInitial,
          loginStateInitial,
      ),
    );
  }
}

Edit: this is the signup state (it does not do much since I was only testing)

@immutable
class SignUpState {
  final bool isError;
  final bool isLoading;
  final bool isLoggedIn;

  SignUpState({
    this.isError,
    this.isLoading,
    this.isLoggedIn,
  });

  factory SignUpState.initial() => SignUpState(
    isLoading: false,
    isError: false,
    isLoggedIn: false,
  );

  SignUpState copyWith({
    @required bool isError,
    @required bool isLoading,
    @required bool isLoggedIn,
  }) {
    return SignUpState (
      isError: isError ?? this.isError,
      isLoading: isLoading ?? this.isLoading,
      isLoggedIn: isLoggedIn ?? this.isLoading,
    );
  }
}

And the signup reducer

signUpReducer(SignUpState prevState, SetSignUpStateAction action) {   
  final payload = action.signUpState;
  print(action); // prints Instance of 'SetSignUpStateAction'
  print(prevState); // prints Instance of 'SignUpState'
  print("signUpReducer");
  return prevState.copyWith(
    isError: payload.isError,
    isLoading: payload.isLoading,
    isLoggedIn: payload.isLoggedIn,
  );
}

The print statement executes

Edit 2:

This is the login reducer, but nothing is going through here yet, I don’t have any login actions implemented

loginReducer(LoginState prevState, SetLoginStateAction action) {
  final payload = action.loginState;

  return prevState.copyWith(
    isError: payload.isError,
    isLoading: payload.isLoading,
  );
}

Solution

The action that you dispatch will be sent to all the reducers. Using checks you can mutate your state easily. This will also prevent unnecessary updates in the state.

Very soon you will have multiple actions being dispatched & handling them as follows will allow you to do that.

Your signUpReducer should look something like this:

signUpReducer(SignUpState prevState, dynamic action) {

  if (action is SetSignUpStateAction) {
    return setSignUpState(prevState, action);
  }
  return prevState;
}

SignUpState setSignUpState(SignUpState prevState, SetSignUpStateAction action) {
    final payload = action.signUpState;
    print(action); // prints Instance of 'SetSignUpStateAction'
    print(prevState); // prints Instance of 'SignUpState'
    print("signUpReducer");
    return prevState.copyWith(
      isError: payload.isError,
      isLoading: payload.isLoading,
      isLoggedIn: payload.isLoggedIn,
    );
}

Your loginReducer should look something like this:

loginReducer(LoginState prevState, dynamic action) {

  if(action is SetLoginStateAction) {
    return setLoginState(prevState, action);
  }
  return prevState;
}

LoginState setLoginState(LoginState prevState, SetLoginStateAction action) {
    final payload = action.loginState;

    return prevState.copyWith(
      isError: payload.isError,
      isLoading: payload.isLoading,
    );
}

Answered By – Ravi Singh Lodhi

Answer Checked By – Willingham (FlutterFixes Volunteer)

Leave a Reply

Your email address will not be published. Required fields are marked *