Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

showing success and error messages in react/redux app

I'm trying to add toast notifications to my app, one plugin I've been trying to use is react-toastify.

The issue I'm having is probably more a general react/redux issue more than with a plugin such as react-toastify.

I'm using a reducer to set the redux state for errors and success messages, from what I understand with the current code, each error or success message is persistent in the store until another action is called to clear them.

The issue I can't figure out is how do I trigger a toast only once. Eg. I enter the wrong credentials, it creates an error toast, but whenever the state changes and reloads (typing anything into the email or password fields) it creates another toast.

How do I get it to only show once?

userActions.js

function handleErrors(res) {
    if (res.ok) {
        return res.json();
    } else {
       return res.json().then(err => {throw err;});
    }
}

export const login = (user) => dispatch => {
    fetch(`${url}/login`, 
    {
        credentials: 'include', 
        method: 'post', 
        body: user, 
        headers: new Headers({
            'Content-Type': 'application/json',
            'Accept': 'application/json'
        })
     })
     .then(handleErrors)
     .then(res =>
         dispatch({
             type: LOGIN,
             payload: res
         })
     )
     .catch(error => 
         dispatch({
             type: ERROR,
             payload: error
         })
     )
}

userReducer.js

const initialState = {
    errors: '',
    success: ''
};

export default function(state = initialState, action) {
    switch (action.type) {

        case LOGIN:
            return {
                ...state,
                errors: '',
                success: action.payload.message
            };

        case ERROR:
            return {
                ...state,
                success: '',
                errors: action.payload.message
            }

        default:
            return state;
        }
}

app.js

app.post('/login', function(req, res) {
    ... return res.status(500).send({ message: 'Wrong credentials' });
    ... return res.status(200).send({ message: 'good!' });
});

login.js

class Login extends React.Component {
    constructor() {
        super();    
        this.state = {
            email: "",
            password: ""
        }
    }

    handleChange = event => {
        this.setState({
            [event.target.id]: event.target.value
        });
    }

    render() {

        const { errors, login, success } = this.props;

        if (success !== '') toast.success(success, {
            position: toast.POSITION.TOP_CENTER
        });

        if (errors !== '') toast.error(errors, {
            position: toast.POSITION.TOP_CENTER
        });

        return (
            <div>
                <input type="text" id="email" placeholder="Email Address" onChange={this.handleChange} />
                <input type="password" id="password" placeholder="Password" onChange={this.handleChange} />
                <button onClick={() => login(JSON.stringify({email: this.state.email, password: this.state.password}))}>Log In</button>
                <ToastContainer />  
            </div>  
        )
    }
}

const mapStateToProps = state => ({
    errors: state.store.errors,
    success: state.store.success
});

export default connect(mapStateToProps, {login})(Login);
like image 645
totalnoob Avatar asked Jul 07 '18 04:07

totalnoob


People also ask

How do I show error message in React Redux?

Displaying the Error We'll create a new file FetchError. js in our components directory. After import ing React , we'll create a new functional stateless component FetchError that will take two props: a message string, and an onRetry function. This component will be the default export for this file.

How do you show a success or error message based on the response of the API in React JS?

The short answer is use an additional "useState" hook to store the messages. Then have a logical operator (in the return section above row-1) to check if messages exists & display them.


1 Answers

You're calling toast.success or toast.error inside render which makes a new toast pop up every time you re-render the component.

The solution is simple. Move your toast calls outside render, where they will only be called once.

One way to achieve this is to return a value from your userAction.

export const login = (user) => dispatch => {
    return new Promise((resolve, reject) => {
        fetch(`${url}/login`, 
        {
            credentials: 'include', 
            method: 'post', 
            body: user, 
            headers: new Headers({
                'Content-Type': 'application/json',
                'Accept': 'application/json'
            })
         })
         .then(handleErrors)
         .then(res => {
                 dispatch({
                     type: LOGIN,
                     payload: res
                 })
                 resolve(res)
             }
         )
         .catch(error => {
                 dispatch({
                     type: ERROR,
                     payload: error
                 })
                 reject(error)
             }
         )
    }
}

Then use that value to toast in login.js.

class Login ... {
    ...
    loginUser = () => {
        this.props.login(JSON.stringify({email: this.state.email, password: this.state.password}))
        .then(res => {
                toast.success(res.message, { position: toast.POSITION.TOP_CENTER })
            }
        ).catch(error => {
                toast.error(error.message, { position: toast.POSITION.TOP_CENTER })
            }
        )
    }
    ...
    render() {
        return (
            ...
            <button onClick={this.loginUser}>Log In</button>
            ...
        )
    }
}

There are other ways to achieve the same functionality and depending on the structure of your project, you may want to toast in a more generalized way.

like image 179
Jemi Salo Avatar answered Nov 16 '22 00:11

Jemi Salo