Conditionally Rendering Material UI Alerts in ReactJS/ ReduxJS

Starting a React project and looking for a way to conditionally render error messages with user forms? One approach I recommend taking is utilizing Material UI's alert system, while leveraging a redux state of user error. If this state is ever set, then you can conditionally render an error message in your component. There are a few nuances to this approach so pay attention carefully, also this blog assumes you are comfortable with ReactJS and Redux and have already created a React App. I am using a Ruby On Rails back end but as long as you are getting JSON promises back, this method should work for you. We will be doing the set up for flexible User Login Errors.

1. Install Material UI's Necessary NPM Packages

Utilize the commands below in your terminal to do so.

npm install @material-ui/core
npm install @material-ui/lab

With those two commands you have access to many Component classes that are in Material UI.

2. Set Up Reducer to Update Redux Store

So now your reducer needs to tell your Redux Store, when to update, and what to update the store with. I set mine up in my SignInReducer which in this project is my reducer for users.

case 'USER_ERROR':
            return {...state, error: action.payload}

        case 'REMOVE_ERROR':
            return {...state, error: {}}

3. Set Up Your Dispatched Actions to Work with Reducer

Wherever you define your actions for the errors you are trying to display depends mostly on what the error messages are displayed for. Since this example is for user login we will put them in the user actions file.

export const authError = e => ({
    type: 'USER_ERROR',
    payload: e
})

export const removeError = () => ({
    type: 'REMOVE_ERROR'
})

Now we have the ability to call an action and pass an argument to it to set an error in the Redux store, and we also have a method to call when we want to get rid of the error message.

4. Call Dispatch Action During Login Action

The process now is to call this action when a user logs in, and if there is an issue with their login (incorrect username or password) then the back end will pass the error message back. You need to ensure that your back end is also set up to do this, so for example I put custom conditionals to take care of this on my Ruby on Rails back end. This action is fired to my sessions controller to create a new user. The part to look at is the elsif and else conditions as they handle the error.

def create

        @user = User.find_by(username: params[:user][:username])

        if @user && @user.authenticate(params[:user][:password])
            @token = encode_token(user_id: @user.id)
            render json: {
                user: {
                id: @user.id,
                email: @user.email,
                username: @user.username,
                password: @user.password,
                logged_in: true
            },
        token: @token,
    status: 200}
        elsif @user
            render json: {
                status: :unprocessable_entity,
                error: "Wrong Password"
            }
        else
            render json: {
                status: :unprocessable_entity,
                error: "Username Not Found"
            }
        end
    end

Now set up your login action to handle this error message that gets passed back. It is easiest to have the conditional set to check for the data status to equal 200. If that status is not 200, the dispatch action we created in step 3 will be called and set the Redux store error to whatever the back end passes back.

Try logging in with incorrect username and password and look at your Redux tool. It should be telling you that the Redux Store under User(if you combined multiple reducers), there should be a state of error set to what your back end is passing back. If you did not combine reducers it should just be under a state of error.

5. Set Up Access To State in Component

To access your state in a class component set up your mapStateToProps as such. You probably need to reference other state in the login, but thats all you need for this error message.

let mapStateToProps = (state) => {
    return ({error: state.signInR.error})
}

Functional components should use useSelector method in order to access the state of error as such. signInR is how I reference the correct reducer in my rootReducer, if you do not use combined reducers just use state.error.

const error = useSelector(state => state.signInR.error)

6. Conditionally Render Alerts Based on Error State

Before anything make sure that you import the Alert Component from Material UI.

import Alert from '@material-ui/lab/Alert';

Then from there set up your return statement to render with an alert if there is a state of error that is longer than 0. Below is an example from my recent project.

render() {
        if(this.props.error && this.props.error.length > 0){
        return (
            <div className="sign-in-div">
                <form className = "sign-in-form" onSubmit={this.handleSubmit}>             
  <Alert key={Math.random(100000)} severity="error">
    {String(this.props.error)}
  </Alert>
                    <h1>Login</h1>
                    <input type = "text" placeholder = "Username" name="username" value={this.username} onChange={this.handleChange}/><br></br>
                    <input type = "password" placeholder = "Password" name="password" value={this.password} onChange={this.handleChange}/><br></br>
                    <input type= "submit" value = "Login"></input>
                </form>
                <Link to="/register">Dont' Have An Account?<br/>Register Here</Link>

            </div>
        )} else {
            return(
                <div className="sign-in-div">
                <form className = "sign-in-form" onSubmit={this.handleSubmit}>
                <h1>Login</h1>
                    <input type = "text" placeholder = "Username" name="username" value={this.username} onChange={this.handleChange}/><br></br>
                    <input type = "password" placeholder = "Password" name="password" value={this.password} onChange={this.handleChange}/><br></br>
                    <input type= "submit" value = "Login"></input>
                </form>
                </div>

Make sure to check for the length to be greater than 0 and also to check if the state exists, or you may keep rendering an error message with an empty object.

7. Removing Error Upon Component Unmounting

So the last part is that you need to ensure that when the component unmounts the error will be deleted. If not then the component may try to rerender an unnecessary error message when the component rerenders or crash your application.
For class components, when your SignInForm unmounts just call the removeError action from step 3. You will need to set up mapDispatchToProps to handle this.

componentWillUnmount = () => {
        this.props.removeError()
    }

In a functional component, just call it as a cleanup function in a useEffect hook.

useEffect(() => {
        return () => {
            dispatch(removeError())
        };
    }, []);

One More Note on Styling

I found that the easiest way to not make your Alert component take up the entire width of the component it is called in is just by calling styling on the component and setting its width to a percentage. There are a few other ways to override styling like justifyContent can move where the text is and changing margins will change where on the page the error message will display.

<Alert key={Math.random(100000)} severity="error" style={{width:'80%'}}>

That is all you should need to be able to render error messages from Material UI's Alert component. This is not the most complete or robust implementation for this component, however, for new developers I found this to be the easiest approach that makes sense. Hope this helps you make some awesome applications that can give your user some level of feedback!

12