bartonhammond/snowflake

Curious how Snowflake is handling Error Alerts

Closed this issue · 4 comments

I was looking through the repository, and was curious how it is displaying the right Error Alerts. It seems like both Register and Login use LoginRender, which shows ErrorAlert during render whenever it receives an error prop.

My question is how does Snowflake know not to display multiple alerts?? Here is the issue I'm facing:

I used to have my own Register and Login components under react-native-router-flux. It seems like whenever a redux state changes, ALL the components under the Router gets rerendered, not just the "current" one. So when a registration fails, for example, I see alerts NOT just from Register, but from all components such as Login.

Here's an example:

main.ios.js

export default class App extends Component {
    render() {
        return (
            <Provider store={store}>
                <Router>
                    <Scene key='root'>
                        <Scene key='register' component={Register} type='replace'>
                        <Scene key='signin' component={SignIn} type='replace'>
                    </Scene>
                </Router>
            </Provider>
        );
    }
}

Register.js

class Register extends Component {
    render() { 
        const { loading, error } = this.props;
        if (!loading && error) {
            Alert.alert('Error from REGISTER');
        }

        return <View>...</View>;
    }
}

const mapStateToProps = (state) => {
    return {
        loading: state.get("authenticationReducer").get("loading"),
        error:   state.get("authenticationReducer").get("error"),
    };
};

export default connect(mapStateToProps)(Register);

SignIn.js

class SignIn extends Component {
    render() { 
        const { loading, error } = this.props;
        if (!loading && error) {
            Alert.alert('Error from SIGNIN');
        }

        return <View>...</View>;
    }
}

const mapStateToProps = (state) => {
    return {
        loading: state.get("authenticationReducer").get("loading"),
        error:   state.get("authenticationReducer").get("error"),
    };
};

export default connect(mapStateToProps)(SignIn);

Whenever a Registration fails and the "error" prop in the redux state updates, both SignIn and Register rerenders since they are both "connected" to flux, and I get 2 popups, one from Register and another from Login. Seems like you guys are taking a similar approach - handling errors&alerting in the render. How did you guys resolve this issue? Thanks!

Hi @yoongeemin. Just to be clear, I believe you are seeing multiple reducers firing not because of the router but rather a design principal of redux. Redux invokes all the reducers w/ the current state and action. Most of the reducers will end up just returning the state as they don't recognize the action.

In the case of Login, in LoginRender, the specific error from auth is checked with this.errorAlert.checkError(this.props.auth.form.error). So the question is then, where did this prop get populated? First, look at Login, where the action login is invoked:

function buttonPressHandler (login, username, password) {
  login(username, password)
}

The authActions calls this:

    return BackendFactory().login({
      username: username,
      password: password
    })

and it results in either a success or it has this:

      .catch((error) => {
        dispatch(loginFailure(error))
      })

Now that error object is what's thrown from sub-class of BackendFactory, for example, Hapi, where the login function is executed. Note that it has a catch of error:

      .catch((error) => {
        throw (error)
      })

Back in authActions, that error object is dispatched:

      .catch((error) => {
        dispatch(loginFailure(error))
      })

In authReducer, the following code sets the error

  case SIGNUP_FAILURE:
    case LOGOUT_FAILURE:
    case LOGIN_FAILURE:
    case RESET_PASSWORD_FAILURE:
      return state.setIn(['form', 'isFetching'], false)
      .setIn(['form', 'error'], action.payload)

Note the last line. That's the specific error for LOGIN_FAILURE and thus LoginRender can process that particular error with the following:

    // display the login / register / change password screens
    this.errorAlert.checkError(this.props.auth.form.error)

I'm not sure that I'm answering your question but this is how Snowflake knows what specific error occurred. All the actions that are dispatched either have a SUCCESS or FAILURE resultant action.

@bartonhammond Thanks so much for the response. Now I understand a little more about the redux flow.

However I guess my question is: when the login fails and the authReducer updates its auth.form.error, doesn't that state change get propagated to ALL the components "connected" to authReducer (listening for change in auth.form.error)?

For example, Login, Register, ForgotPassword, and Logout each render instance of LoginRender. So shouldn't we be seeing 4 error alerts at the same time?? That's the exact problem I'm facing: i am seeing 2 alerts, one from Login and one from Register..

authReducer only has "error" state and doesnt distinguish between loginError, registerError, or logoutError, right? So I guess my question is when "error" gets updated in authReducer, how do you tell only ONE component to respond to that state change, instead of all 4 components that listen to changes in the "error" state?

Not sure if I explained that right :( much thanks amd appreciate your guidance

However I guess my question is: when the login fails and the authReducer updates its auth.form.error, doesn't that state change get propagated to ALL the components "connected" to authReducer?

No! I think you are confusing component vs reducer. Redux will pass each action and current state to all the reducers but only one reducer, authReducer,handles the action type of LOGIN_FAILURE! The only component processing is the one currently displayed.

I'm not sure how you are handling the propagation of the errors but in Snowflake they are at a fine level - the errors are tied to a specific action.

authReducer only has "error" and doesnt distinguish between logimError, registerError, or logoutError, right? So I guess my question is when "error" gets updated in authReducer, how do you tell only ONE component to respond to that state change??

True! authReducer processes certain actions the same:

  case SIGNUP_FAILURE:
    case LOGOUT_FAILURE:
    case LOGIN_FAILURE:
    case RESET_PASSWORD_FAILURE:
      return state.setIn(['form', 'isFetching'], false)
      .setIn(['form', 'error'], action.payload)

If I understand your question, there's only one component "live" at the time. For example, Register calls the authActions.signup method which dispatches a SIGNUP_FAILURE which LoginRender then displays.

Does that help?

I believe this has been answered