Handle empty image field upload to Cloudinary - reactjs

In this project I have a Redux Form with an input type='file' to upload an image to Cloudinary along with the other data upload to MongoLab. The form is reused with an item (Book) creation component (booklist\client\src\components\AddBook.js) and the one for editing a Book (booklist\client\src\components\Book.js).
The AddBookForm form. The book prop when present comes from the parent Book component. The other Fields were omitted.
class AddBookForm extends Component {
componentDidMount() {
this.handleInitialize();
}
handleInitialize() {
let names = '';
if (this.props.book) {
const authors = this.props.book.authors && this.props.book.authors.map(ath => {
let str = `${ath.firstname} ${ath.lastname}, `;
names = names + str;
});
names = names.slice(0, -2);
}
const initData = {
'title': this.props.book && this.props.book.title || '',
'pages': this.props.book && this.props.book.pages || 0,
'publisher': this.props.book && this.props.book.publisher || '',
'publishedAt': this.props.book && moment(this.props.book.publishedAt).format('MM.DD.YYYY') || '',
'releasedAt': this.props.book && moment(this.props.book.releasedAt).format('MM.DD.YYYY') || '',
'isbn13': this.props.book && this.props.book.isbn13 || '',
'cover': this.props.book && this.props.book.cover || '',
'authors': names,
book_id: this.props.book && this.props.book._id,
cloudinarySecureUrl: this.props.book && this.props.book.cloudinarySecureUrl
};
this.props.initialize(initData);
}
render() {
const { onSubmit, handleSubmit, pristine, reset, submitting } = this.props;
return (
<form onSubmit={handleSubmit(onSubmit)}>
<Field
name='cover'
type='file'
component={fileField}
label='Cover'
comment='Please provide a cover. Optional'
/>
<button
disabled={submitting}
className='add-form-action'
>
Add Book
</button>
<button type='button' disabled={pristine || submitting} onClick={reset}>
Clear Values
</button>
</form>
)
}
export default AddBookForm = reduxForm({
form: 'AddBookForm'
})(AddBookForm);
The Book component method for handling the form submission
onSubmit(formData) {
const authors = formData.authors;
const authorsToArray = [];
const authorsArray = authors.split(',');
for (let ath of authorsArray) {
const firstname = ath.trim().split(' ')[0];
const lastname = ath.trim().split(' ')[1] || '';
authorsToArray.push({
firstname,
lastname
});
}
formData.authors = authorsToArray;
this.props.addBook(formData, this.props.history);
}
This is the addBook() action to handle the Book form data upload.
export const addBook = (bookData, history) => (dispatch) => {
const cloudinaryUrl = 'https://api.cloudinary.com/v1_1/*******/upload';
const cloudinaryUploadPreset = '*******';
const formData = new FormData();
formData.append('file', bookData.cover[0]);
formData.append('upload_preset', cloudinaryUploadPreset);
axios({
url: cloudinaryUrl,
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
data: formData
})
.then(res => {
bookData.cloudinarySecureUrl = res.data.secure_url;
axios.post('/api/books', bookData)
.then(res => {
history.push('/')
})
.catch(err => dispatch({
type: GET_ERRORS,
payload: err.response.data
}));
})
.catch(error => console.log('Cloudinary image upload error:', error.message));
};
What do I default bookData.cover[0] to so I can submit the form with no image for the Field name='cover'? Is there/should I take another way? The complete repo is on https://github.com/ElAnonimo/booklist
UPDATE
With this addition to the catch clause of the addBook() action on no image upload when editing a Book I'm getting the correct list of books even the image persists from when the image was uploaded.
.catch(error => {
history.push('/');
});

This is what I did in the book creation action.
// add book
export const addBook = (bookData, history) => (dispatch) => {
const cloudinaryUrl = 'https://api.cloudinary.com/v1_1/dlzbcvsbf/upload';
const cloudinaryUploadPreset = 'hvqidzpj';
const formData = new FormData();
// bookData.cover is the `FileList` array created when user submits a cover picture, bookData.cover[0] is the actual cover picture file
bookData.cover && bookData.cover[0] && formData.append('file', bookData.cover[0]);
formData.append('upload_preset', cloudinaryUploadPreset);
if (!bookData.cover[0]) {
axios.post('/api/books', bookData)
.then(res => {
history.push('/')
})
.catch(err => dispatch({
type: GET_ERRORS,
payload: err.response.data
}));
} else {
axios({
url: cloudinaryUrl,
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
data: formData
})
.then(res => {
// book cover isn't stored in the DB no need to send it to backend
delete bookData.cover;
bookData.cloudinarySecureUrl = res.data.secure_url;
axios.post('/api/books', bookData)
.then(res => {
history.push('/')
})
.catch(err => dispatch({
type: GET_ERRORS,
payload: err.response.data
}));
})
.catch(error => {
console.log('Cloudinary image upload error:', error.message);
history.push('/');
});
}
};

Related

Upload files React.js

Save images in the MongoDB database after uploading from the React.js.
I want to save the file's path(only) along with other data like template's name, package_ids etc...
Following is the templateCreate action:
export const createTemplate = (templateData) => async (dispatch, getState) => {
try {
dispatch({ type: TEMPLATE_CREATE_REQUEST });
const {
adminLogin: { adminInfo },
} = getState();
const config = {
url: "http://localhost:8000/v1/template/create",
method: "POST",
data: templateData,
headers: {
"Content-Type": "application/json",
"x-access-token": adminInfo.data.JWToken,
},
};
const { data } = await axios(config).catch(console.error);
dispatch({ type: TEMPLATE_CREATE_SUCCESS, payload: data });
} catch (error) {
dispatch({
type: TEMPLATE_CREATE_FAILURE,
payload:
error.response && error.response.data.message
? error.response.data.message
: error.message,
});
}
};
The createTempleteReducer function:
export const templateCreateReducer = (state = {}, action) => {
switch (action.type) {
case TEMPLATE_CREATE_REQUEST:
return { loading: true };
case TEMPLATE_CREATE_SUCCESS:
return { loading: false, success: true, template: action.payload };
case TEMPLATE_CREATE_FAILURE:
return { loading: false, error: action.payload };
default:
return state;
}
};
The templateScreen:
return (
<>
<FormContainer>
<h2 className="template-h2">Create New Template</h2>
<Form onSubmit={submitHandler}>
<Form.Group controlId="preview_image">
<Form.Label>Select Preview Image</Form.Label>
<Form.Control
type="file"
accept=".png, .jpg, .jpeg .pdf"
name="preview_image"
/>
</Form.Group>
...other fields
</Form>
</FormContainer>
</>
);
The submitHandler function:
const submitHandler = (e) => {
e.preventDefault();
const formData = new FormData();
formData.append("name", name);
formData.append("package_ids", JSON.stringify(package_ids));
formData.append("preview_image", preview_image);
formData.append("html_template", html_template);
formData.append("required_variables", required_variables);
console.log(preview_image);
const config = {
headers: {
"Content-Type": "application/json",
"x-access-token": adminInfo.data.JWToken,
},
};
dispatch(createTemplate(formData));
};
It saves the data to the database but preview_image is saved as 'C:\\fakepath\\2021_Facebook_icon.jpg' I'm not sure from where the fakepath came. and it is also not uploading the image in the uploads folder
The useEffect hook which currently just lists the package data from the database:
useEffect(() => {
dispatch(listPackages());
}, [dispatch]);
Now what to do next to save all the data to the database using useEffect and createTemplate action function. I am using multer in the backend(Node.js)

Update the likes array in a post in the frontend

I have a PUT route in the backend for liking posts, it adds the users id to the likes array in the post. This works fine when tested on Postman (by providing the post in the body) and the likes array is updated. However, when the icon is clicked in the frontend, I want the likes array to update but I'm not sure how to update the state for the post. result is showing the response in the frontend with a 200 status code but that's as far as I'm getting.
How can I update the likes array in the frontend?
Post.js
const Post = (props) => {
const [post, setPost] = useState({});
const [error, setError] = useState(false);
const id = props.match.params.id;
const loadSinglePost = (id) => {
read(id).then((data) => {
if (error) {
console.log(data.error);
setError(data.error);
} else {
setPost(data);
console.log(data);
}
});
};
useEffect(() => {
loadSinglePost(id);
}, [props]);
const like = (id) => {
const {user: { _id }, token} = isAuthenticated();
fetch(`${API}/like/${_id}`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({
id: id,
}),
})
.then(result => { console.log(result)})
.catch((err) => {
console.log(err);
});
};
return (
<div>
<Navbar />
<div>
<h3>{post && post.title}</h3>
<p>
{post && post.author ? post.author.name : ""}
</p>
<p>{post && post.body}</p>
<h5>{post && post.likes && post.likes.length} likes</h5>
<img
onClick={() => {
like(id);
}}
alt="..."
/>
</div>
</div>
);
};
export default Post;
controllers/post.js
exports.like = (req, res) => {
Post.findByIdAndUpdate(req.body._id, {
$push: {likes: req.profile._id}
}, {new: true}).exec((err, result) => {
if (err) {
return res.status(422).json({error: err})
} else {
return res.json(result)
}
})
}
exports.readById = (req, res) => {
const id = req.params.id
Post.findById(id)
.then(post => res.json(post))
.catch(err => res.status(400).json('Error: ' + err));
}
You can update likes in post in then callback like this:
const like = (id) => {
const {user: { _id }, token} = isAuthenticated();
fetch(`${API}/like/${_id}`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({
id: id,
}),
})
.then(result => {
// here update post likes
let updatedPost = {...post}; //to make copy of post
updatedPost.likes = [...updatedPost.likes, id]; //add new id to updatedPost' likes array
setPost(updatedPost); //update post
console.log(result)
})
.catch((err) => {
console.log(err);
});
};
Also from front-end you're sending id key in body:
body: JSON.stringify({
id: id, // here
})
And at back end you're expecting _id
Post.findByIdAndUpdate(req.body._id, { // here
$push: {likes: req.profile._id}
}

React + Express sending data?

I'm working on a login/register system with React front-end and Node/Express back-end.
When the registration fails, i want to reload the registration page and return the error msgs to my react front end.
Instead of refreshing the page, it renders the JSON data.
How can you go about re-rendering the registration form, while sending the data behind the scenes?
router.post("/register", (req, res) => {
const { name, email, password, password2 } = req.body;
let errors = [];
...
...
...
if (errors.length > 0) {
res.send({
errors,
name,
email,
password,
password2
});
You need to receive the response from express in your react app, and perform your logic inside your submit function in react.
One way you can do this is to create a state variable for the error message. Make the initial value null or ''. When user registration fails catch the error and assign it your error variable in state. Here is an example of user registration system:
//RegistrationForm.js
import React, { useState } from 'react';
import AuthApiService from '../../services/auth-api-service';
import { Input } from '../Utils/Utils';
import { Button } from '#material-ui/core';
import './RegistrationForm.css';
export default function RegistrationForm(props) {
const [error, setError] = useState(null)
const errorDiv = error
? <div className="error">
<i class="material-icons error-icon">error_outline</i>
{error}
</div>
: '';
const handleSubmit = e => {
e.preventDefault();
setError(null);
const { full_name, user_name, password } = e.target;
AuthApiService.postUser({
full_name: full_name.value,
user_name: user_name.value,
password: password.value
})
.then(user => {
full_name.value = '';
user_name.value = '';
password.value = '';
props.onRegistrationSuccess();
})
.catch(res => {
setError(res.error);
})
};
return(
<form className='RegistrationForm'
onSubmit={handleSubmit}
>
<div className='full_name'>
<label htmlFor='RegistrationForm__full_name'>
Full name
</label>
<Input
name='full_name'
type='text'
required
id='RegistrationForm__full_name'>
</Input>
</div>
<div className='user_name'>
<label htmlFor='RegistrationForm__user_name'>
User name
</label>
<Input
name='user_name'
type='text'
required
id='RegistrationForm__user_name'>
</Input>
</div>
<div className='password'>
<label htmlFor='RegistrationForm__password'>
Password
</label>
<Input
name='password'
type='password'
required
id='RegistrationForm__password'
>
</Input>
</div>
<div className='confirm-password'>
<label htmlFor="LoginForm__confirm-password">
Retype Password
</label>
<Input
name='confirm-password'
type="password"
required
id="LoginForm__confirm-password">
</Input>
</div>
{errorDiv}
<Button type='submit' variant='contained' color='default'>
Register
</Button>
</form>
)
}
//AuthApiService.js
import config from '../config';
import TokenService from './token-service';
import IdleService from './idle-service';
const AuthApiService = {
postUser(user) {
return fetch(`${config.API_ENDPOINT}/users`, {
method: 'POST',
headers: {
'content-type': 'application/json',
},
body: JSON.stringify(user)
})
.then(res =>
(!res.ok)
? res.json().then(e => Promise.reject(e))
: res.json()
)
.then(res => {
TokenService.saveAuthToken(res.authToken)
IdleService.registerIdleTimerResets()
TokenService.queueCallbackBeforeExpiry(() => {
AuthApiService.postRefreshToken()
})
return res
})
},
postLogin({user_name, password }) {
return fetch(`${config.API_ENDPOINT}/auth/login`, {
method: 'POST',
headers: {
'content-type': 'application/json'
},
body: JSON.stringify({user_name, password}),
})
.then(res =>
(!res.ok)
? res.json().then(e => Promise.reject(e))
: res.json()
)
.then(res => {
TokenService.saveAuthToken(res.authToken)
TokenService.queueCallbackBeforeExpiry(() => {
AuthApiService.postRefreshToken()
})
return res
})
},
postGuestLogin() {
return fetch(`${config.API_ENDPOINT}/auth/guest-login`, {
method: 'POST',
headers: {
'content-type': 'application/json'
},
})
.then(res =>
(!res.ok)
? res.json().then(e => Promise.reject(e))
: res.json()
)
.then(res => {
TokenService.saveAuthToken(res.authToken)
TokenService.queueCallbackBeforeExpiry(() => {
AuthApiService.postRefreshToken()
})
return res
})
},
postRefreshToken() {
return fetch(`${config.API_ENDPOINT}/auth/refresh`, {
method: 'POST',
headers: {
'authorization': `Bearer ${TokenService.getAuthToken()}`,
},
})
.then(res =>
(!res.ok)
? res.json().then(e => Promise.reject(e))
: res.json()
)
.then(res => {
TokenService.saveAuthToken(res.authToken)
TokenService.queueCallbackBeforeExpiry(() => {
AuthApiService.postRefreshToken()
})
return res
})
.catch(err => {
TokenService.clearAuthToken()
console.log('refresh token request error')
console.log(err)
})
}
}
export default AuthApiService;
//TokenService.js
import jwtDecode from 'jwt-decode';
import config from '../config';
let _timeoutId;
const _TEN_SECONDS_IN_MS = 10000;
const TokenService = {
saveAuthToken(token) {
window.localStorage.setItem(config.TOKEN_KEY, token);
},
getAuthToken() {
return window.localStorage.getItem(config.TOKEN_KEY);
},
clearAuthToken() {
window.localStorage.removeItem(config.TOKEN_KEY);
},
hasAuthToken() {
return !!TokenService.getAuthToken()
},
makeBasicAuthToken(userName, password) {
return window.btoa(`${userName}:${password}`);
},
parseJwt(jwt) {
return jwtDecode(jwt);
},
readJwtToken() {
return TokenService.parseJwt(TokenService.getAuthToken());
},
_getMsUntilExpiry(payload) {
return (payload.exp * 1000) - Date.now();
},
queueCallbackBeforeExpiry(callback) {
const msUntilExpiry = TokenService._getMsUntilExpiry(
TokenService.readJwtToken()
);
_timeoutId = setTimeout(callback, msUntilExpiry - _TEN_SECONDS_IN_MS);
},
clearCallbackBeforeExpiry() {
clearTimeout(_timeoutId);
}
};
export default TokenService;
EDIT:
Here is an example of what the router can look like:
//users-router.js
const express = require('express')
const path = require('path')
const UsersService = require('./users-service')
const AuthService = require('../auth/auth-service')
const usersRouter = express.Router()
const jsonBodyParser = express.json()
usersRouter.post('/', jsonBodyParser, (req, res, next) => {
const { password, user_name, full_name } = req.body
for (const field of ['password', 'full_name', 'user_name'])
if (!req.body[field])
return res.status(400).json({
error: `Missing ${field} in request body`
})
const passwordError = UsersService.validatePassword(password)
if (passwordError)
return res.status(400).json({ error: passwordError })
UsersService.hasUserWithUserName(
req.app.get('db'),
user_name
)
.then(hasUserWithUserName => {
if(hasUserWithUserName)
return res.status(400).json({ error: 'Username already taken' })
return UsersService.hashPassword(password)
.then(hashedPassword => {
const newUser = {
user_name,
full_name,
password: hashedPassword,
date_created: 'now()'
}
return UsersService.insertUser(
req.app.get('db'),
newUser
)
.then(user => {
const serializedUser = UsersService.serializeUser(user)
const { user_name, user_id } = serializedUser
const sub = user_name
const payload = {user_id: user_id}
res
.status(201)
.location(path.posix.join(req.originalUrl, `/${user.id}`))
.send({
authToken: AuthService.createJwt(sub, payload),
user: serializedUser
})
})
})
})
.catch(next)
})
module.exports = usersRouter
//users-service.js
const bcrypt = require('bcryptjs')
const xss = require('xss')
const REGEX_UPPER_LOWER_NUMBER_SPECIAL = /(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!##\$%\^&])[\S]+/
const UsersService = {
hasUserWithUserName(db, user_name) {
return db('askify_users')
.where({ user_name })
.first()
.then(user => !!user)
},
insertUser(db, newUser) {
return db
.insert(newUser)
.into('askify_users')
.returning('*')
.then(([user]) => user)
},
validatePassword(password) {
if (password.length < 8) {
return 'Password must be longer than 8 characters'
}
if (password.length > 72) {
return 'Password must be less than 72 characters'
}
if (password.startsWith(' ') || password.endsWith(' ')) {
return 'Password must not start or end with empty spaces'
}
if (!REGEX_UPPER_LOWER_NUMBER_SPECIAL.test(password)) {
return 'Password must contain one upper case, lower case, number and special character'
}
return null
},
hashPassword(password) {
return bcrypt.hash(password, 12)
},
serializeUser(user) {
return {
id: user.id,
full_name: xss(user.full_name),
user_name: xss(user.user_name),
nickname: xss(user.nick_name),
date_created: new Date(user.date_created),
}
},
}
module.exports = UsersService
// auth-service.js
const bcrypt = require('bcryptjs')
const jwt = require('jsonwebtoken')
const config = require('../config')
const AuthService = {
getUserWithUserName(db, user_name) {
return db('askify_users')
.where({user_name})
.first()
},
comparePasswords(password, hash) {
return bcrypt.compare(password, hash)
},
createJwt(subject, payload) {
return jwt.sign(payload, config.JWT_SECRET, {
subject,
expiresIn: config.JWT_EXPIRY,
algorithm: 'HS256',
})
},
verifyJwt(token) {
return jwt.verify(token, config.JWT_SECRET, {
algorithms: ['HS256'],
})
},
parseBasicToken(token) {
return Buffer
.from(token, 'base64')
.toString()
.split(':')
},
}
module.exports = AuthService

React - TypeError: Cannot read property 'includes' of undefined

I'm new to React, sorry if this is too basic.
I have an input form and I'm trying to handle submits and changes to it, like so:
import { editMenuFormRules } from './forms/form-rules.js';
class Seeds extends Component{
constructor (props) {
super(props);
this.state = {
formData: {
coffee:''
},
menu:[],
editMenuFormRules:editMenuFormRules,
};
this.handleSubmitCoffees = this.handleSubmitCoffees.bind(this);
this.handleBeanFormChange = this.handleBeanFormChange.bind(this);
};
componentDidMount() {
if (this.props.isAuthenticated) {
this.getSeeds();
}
};
getSeeds(event) {
const {userId} = this.props
const options = {
url: `${process.env.REACT_APP_WEB_SERVICE_URL}/seeds/${userId}`,
method: 'get',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${window.localStorage.authToken}`
}
};
return axios(options)
.then((res) => {
console.log(res.data.data)
this.setState({
menu: res.data.data[0].menu
})
})
.catch((error) => { console.log(error); });
};
for handling submit and form change, I have:
handleSubmitCoffees(event) {
event.preventDefault();
const formType = this.props.formType
const {userId} = this.props
var headers = {
'Content-Type': 'application/json',
Authorization: `Bearer ${window.localStorage.authToken}`
}
const data = {
coffee: this.state.formData.coffee
};
if (formType === 'EditMenu' && this.state.menu.includes(this.state.formData.coffee)) {
alert('This coffee already exists. Please add a new one.');
return (<Redirect to='/seeds' />);
};
const url = `${process.env.REACT_APP_WEB_SERVICE_URL}/edit_menu/${userId}`;
axios.post(url, data, headers)
.then((res) => {
this.clearForm()
console.log(data);
})
.catch((err) => {
if (formType === 'EditCoffee') {
this.props.createMessage('Coffee edit failed.', 'Please review your entry');
};
});
};
and:
handleBeanFormChange(event) {
console.log(event)
const obj = this.state.formData;
obj[event.target.name] = event.target.value;
this.setState(obj);
this.validateForm();;
};
finally, my form:
<form onSubmit={ (event) => this.handleSubmitCoffees(event) }>
<div className="field">
<input
name="coffee"
className="input is-large"
type="text"
placeholder="Enter Coffee Name"
value={this.state.formData.coffee}
onChange={this.handleBeanFormChange}
/>
</div>
</form>
when I input my first item at form, however, I'm getting the following error:
TypeError: Cannot read property 'includes' of undefined
which points to this line:
> 150 | if (formType === 'EditMenu' && this.state.menu.includes(this.state.formData.coffee)) {
am I not defining this.state.formData.coffee when I press enter at form?
What am I missing?
const obj = this.state.formData;
obj[event.target.name] = event.target.value;
this.setState(obj); // <-- this is setting 'target.name: value'
This is effectively overwriting formData. I think what you are meaning to do is:
const obj = Object.assign({}, this.state.formData);
obj[event.target.name] = event.target.value;
this.setState({ formData: obj });
Do note that it's important to clone the formData object, as what you are doing is mutating the state, which is not desired.
The problem is here, where there is a GET request for 'menu' value:
componentDidMount() {
if (this.props.isAuthenticated) {
this.getSeeds(); ///// <------------
}
};
There was a malformed json response object at backend:
response_object = {
'status': 'success',
'message': 'User does not have a menu yet',
'data': [{"id": user.id,
"seeds": user.seeds,
"content": template}]
}
//"menu": [] //----> key, value was missing
Therefore, there was no 'menu' value being fetched, and that's what 'undefined' refers to:
//GET request at `getSeeds()`
this.setState({
menu: res.data.data[0].menu // <----- undefined
})
no this.state.menu could be set at getSeeds(), at all.
Adding "menu": [] to response_object fixed it.

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);
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.

Resources