React Hooks fetch post request memory leak - reactjs

I'm trying to post a login form to my API after a validation callback (useForm) and set the response data in my authentication context (dispatch({ type: "SET_AUTH", payload: responseJson }). It works fine but I still get a memory leak warning. I've been working on this problem for a few days and i think i'm going crazy. I tried using ref and AbortController but can't fix this warning. Thanks for your help !
const Signin: React.FC = () => {
const { dispatch } = useAuthContext()
const isMountedRef = useRef<any>(null)
const defaultValue = {
email: '',
password: ''
}
const signinDefaultState = {
loading: false,
error: undefined,
data: undefined
}
type SigninState<T> = {
loading: boolean,
error?: string,
data?: T
}
const [signinState, signinSetState] = useState<SigninState<any>>(signinDefaultState)
const [validFormData, setValidFormData] = useState(defaultValue)
const validFormCallback = () => {
setValidFormData(values)
}
const { values, errors, handleChange, handleKeyup, handleSubmit } = useForm(
defaultValue,
validFormCallback,
validate,
)
useEffect(() => {
const abortController = new AbortController()
isMountedRef.current = true
if (validFormData.email !== "") {
const postSignin = async () => {
const options = {
...authJsonHeader,
method: "POST",
body: JSON.stringify(validFormData),
} as any
try {
signinSetState({ ...signinState, loading: true })
const response = await fetch(`${process.env.REACT_APP_API_HOST}/signin`, { ...options, signal: abortController.signal })
const responseJson = await response.json()
if (!response.ok) {
throw responseJson
}
if (isMountedRef.current) {
dispatch({ type: "SET_AUTH", payload: responseJson })
}
//signinSetState({ ...signinState, data: responseJson as string, loading: false })
} catch (err: any) {
console.log(err)
if (!abortController.signal.aborted) {
signinSetState({ ...signinState, error: err as string, loading: false })
}
}
}
postSignin()
}
return () => {
isMountedRef.current = false
abortController.abort()
}
}, [validFormData])
return (
<form>
<H3>Connecte-toi pour retrouver toutes tes popotes favorites et découvrir celles de tes proches</H3>
<div>
<InputField
id="email"
label="Email"
type="email"
name="email"
value={values.email}
onKeyUp={handleKeyup}
onChange={handleChange}
error={(errors as any).email}
/>
</div>
<div>
<InputField
id="password"
label="Password"
type="password"
name="password"
value={values.password}
onKeyUp={handleKeyup}
onChange={handleChange}
error={(errors as any).password}
/>
</div>
<div style={{ marginTop: "20px" }}>
<Button type="primary" onClick={handleSubmit}>Sign In</Button>
{signinState.error && <InfoBlockStyled>{signinState.error as any}</InfoBlockStyled>}
{signinState.loading && <LoadingInline />}
</div>
</form>
)
}
const InfoBlockStyled = styled(InfoBlock)(({ theme }) => ({
marginTop: theme.spacer.m,
}))
export default Signin

Related

Heroku not running like it should after trying to post data i get a json object loading instead of a page

I'm having problems with heroku. After i try to post data (post a listing), instead of being redirected to listings page where you would find a listing I just posted (that's how it works when i run it locally), the page displays a json object. And refresh doesn't work, i need to type the address again and then everything works normally, and the listing that i just posted is there as it should.
This is the front end
import { useState } from 'react'
import axios from 'axios'
import { useNavigate } from 'react-router-dom'
const PostListings = () => {
let navigate = useNavigate()
const [ newList, setNewListing ] = useState({
city: '',
neighborhood: '',
bedrooms: '',
price: '',
img: '',
reviews_id: []
})
const getNewListing = async () => {
console.log(newList)
await axios({
url: `${window.location.origin}/listings`,
method: 'post',
data: newList
})
}
const handleChange = (e) => {
setNewListing({...newList, [e.target.name]: e.target.value })
console.log(e.target.name)
console.log(e.target.value)
console.log(newList)
}
const handleSubmit= () => {
getNewListing()
navigate('/listings')
window.location.reload(false)
}
return (
<div>
<h2>Add A New Listing</h2>
<form className="submit-form" onSubmit={handleSubmit}>
<input type="text" value={newList.city} onChange={handleChange} name={'city'} placeholder={'city'} />
<input type="text" value={newList.neighborhood} onChange={handleChange} name={'neighborhood'} placeholder={'neighborhood'} />
<input type="text" value={newList.img} onChange={ handleChange} name={'img'} placeholder={'image'} />
<input type="text" value={newList.price} onChange={ handleChange} name={'price'} placeholder={'price'} />
<input type="text" value={newList.bedrooms} onChange={ handleChange} name={'bedrooms'} placeholder={'bedrooms'} />
<button>Submit</button>
</form>
</div>
)
}
export default PostListings
//this is in Controllers
const postListing = async (req, res) => {
try {
console.log('data:', req.body)
const listing = await new Listing(req.body)
console.log('new:', listing)
await listing.save()
return res.status(201).json({ listing })
} catch (error) {
return res.status(500).json({ error: error.message })
}
}
//and in index.js
app.post('/listings', listingsController.postListing)

React hooks abort fetch post

when i submit my login form, i get a memory leak warning message when i set my authentication context with response data dispatch({type: "SET_AUTH", payload: responseJson}). I tried to abort the asynchronous request using abortController but can't fix this warning. Thanks for your help, this issue is driving me crazy.
type SigninState<T> = {
loading: boolean,
error?: string,
data?: T
}
const Signin: React.FunctionComponent = () => {
const { dispatch } = useAuthContext()
const defaultValue = {
email: '',
password: ''
}
const signinDefaultState = {
loading: false,
error: undefined,
data: undefined
}
const [signinState, signinSetState] = useState<SigninState<any>>(signinDefaultState)
const [validFormData, setValidFormData] = useState(defaultValue)
const validFormCallback = () => {
setValidFormData(values)
}
const { values, errors, handleChange, handleKeyup, handleSubmit } = useForm(
defaultValue,
validFormCallback,
validate,
)
useEffect(() => {
const abortController = new AbortController()
if (validFormData.email !== "") {
const postSignin: any = async () => {
const options = {
...authJsonHeader,
method: "POST",
body: JSON.stringify(validFormData),
} as any
try {
signinSetState({...signinState, loading: true})
const response = await fetch(`${process.env.REACT_APP_API_HOST}/signin`, { ...options, signal: abortController.signal })
const responseJson = await response.json()
if (!response.ok) {
throw responseJson
}
dispatch({type: "SET_AUTH", payload: responseJson})
//signinSetState({...signinState, loading: false, data: responseJson})
} catch (err: any) {
if (!abortController.signal.aborted) {
signinSetState({...signinState, loading: false, error: err})
}
}
}
postSignin()
}
return () => abortController.abort()
}, [validFormData])
return (
<form>
<div>
<InputField
id="email"
label="Email"
type="email"
name="email"
value={values.email}
onKeyUp={handleKeyup}
onChange={handleChange}
error={(errors as any).email}
/>
</div>
<div>
<InputField
id="password"
label="Password"
type="password"
name="password"
value={values.password}
onKeyUp={handleKeyup}
onChange={handleChange}
error={(errors as any).password}
/>
</div>
<div style={{ marginTop: "20px" }}>
<Button type="primary" onClick={handleSubmit}>Sign In</Button>
{signinState.error && <InfoBlockStyled>{signinState.error as any}</InfoBlockStyled>}
{signinState.loading && <LoadingInline />}
</div>
</form>
)
}
const InfoBlockStyled = styled(InfoBlock)(({ theme }) => ({
marginTop: theme.spacer.m,
}))
export default Signin

TypeError: Cannot read property 'filename' of undefined 'getting this error while creating a User'

I am trying to create a User with a profile picture. I am using multer, I have no idea how to use multer I just followed the documentation, But when I try to create a user with a profile picture it shows me an error TypeError: Cannot read property 'filename' of undefined. I am using MongoDB, express.js as a backend.
Here is my code
models (user.js)
const mongoose = require('mongoose');
const bcrypt = require('bcrypt');
const userSchema = new mongoose.Schema({
firstName: {
type: String,
required: true,
trim: true,
min: 3,
max: 20
},
lastName: {
type: String,
required: true,
trim: true,
min: 3,
max: 20
},
username: {
type: String,
required: true,
trim: true,
unique: true,
index: true,
lowercase: true
},
email: {
type: String,
required: true,
trim: true,
unique: true,
lowercase: true
},
hash_password: {
type: String,
required: true
},
profilePicture: { type: String }
}, { timestamps: true });
userSchema.virtual('fullName').get(function () {
return `${this.firstName} ${this.lastName}`
});
userSchema.methods = {
authenticate: async function (password) {
return await bcrypt.compare(password, this.hash_password);
}
};
module.exports = mongoose.model('User', userSchema);
controller(auth.js)
const User = require('../models/user');
const bcrypt = require('bcrypt');
const shortid = require('shortid');
exports.signup = async (req, res) => {
User.findOne({ email: req.body.email }).exec(async (error, user) => {
if (error) return res.status(400).json({ error })
if (user)
return res.status(400).json({
message: "User already registered",
});
const { firstName, lastName, email, password } = req.body;
const hash_password = await bcrypt.hash(password, 10);
const profilePicture = req.file.filename
const userSignUp = {
firstName,
lastName,
email,
hash_password,
profilePicture: profilePicture,
username: shortid.generate(),
}
const _user = new User(userSignUp);
_user.save((error, data) => {
if (error) {
return res.status(400).json({
message: error,
});
}
if (data) {
return res.status(201).json({
message: "user created Successfully..!",
});
}
});
});
};
router(auth.js)
const express = require('express');
const { signup, signin, signout } = require('../controller/auth');
const { validateSignupRequest, isRequestValidated, validateSigninRequest } = require('../../validators/auth');
const router = express.Router();
const path = require('path');
const multer = require('multer');
router.use(express.static(__dirname+ '../../uploads'));
const storage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, '../../uploads')
},
filename: (req,file, cb) => {
cb(null, file.filename + "_" + Date.now() + path.extname(file.originalname));
}
});
const upload = multer({ storage: storage }).single('profilePicture');
router.post('/signup', validateSignupRequest,isRequestValidated,upload,signup);
module.exports = router
index.server.js
const express = require('express');
const mongoose = require('mongoose');
const env = require('dotenv');
const cors = require('cors');
const app = express();
env.config();
// connecting to mongoose database
mongoose.connect(process.env.DATABASE,
{
useNewUrlParser:true,
useUnifiedTopology:true,
useCreateIndex: true,
useFindAndModify: false
}).then(() => {
console.log('database connected successfully')
}).catch((err) => {
console.log(err)
});
// routes
const authRoutes = require('./Home_Search_Client/routes/auth');
// middlewares
app.use(cors());
app.use(express.json());
app.use('/api', authRoutes);
// PORT NUMBER
app.listen(process.env.PORT, () => {
console.log(`server is running at port: ${process.env.PORT}`)
});
Frontend (React, Redux)
actions(user.action.js)
import axios from '../helpers/axios';
import { userContants } from './constants';
export const signup = (user) => {
console.log(user)
return async (dispatch) => {
dispatch({ type: userContants.USER_REGISTER_REQUEST });
const res = await axios.post(`/signup`, {
...user
});
if (res.status === 201) {
const { message } = res.data;
dispatch({
type: userContants.USER_REGISTER_SUCCESS,
payload: { message }
});
} else {
if (res.status === 400) {
dispatch({
type: userContants.USER_REGISTER_FAILURE,
payload: { error: res.data.error }
});
}
}
}
};
reducers(user.reducer.js)
import { userContants } from "../actions/constants"
const initState = {
error: null,
message: '',
loading: false
}
export default (state = initState, action) => {
switch (action.type) {
case userContants.USER_REGISTER_REQUEST:
state = {
...state,
loading: true
}
break;
case userContants.USER_REGISTER_SUCCESS:
state = {
...state,
loading: false,
message: action.payload.message
}
break;
case userContants.USER_REGISTER_FAILURE:
state = {
...state,
loading: false,
error: action.payload.error
}
break;
}
return state;
}
containers (Signup.js)
import React, { useEffect, useState } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { Button, Col, Container, Form, Row } from 'react-bootstrap'
import Layout from '../../Components/Layout'
import Input from '../../Components/UI/Input'
import { signup } from '../../actions'
import { Redirect } from 'react-router-dom';
const Signup = (props) => {
const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState('');
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [profilePicture, setProfilePicture] = useState('');
const auth = useSelector((state) => state.auth)
const user = useSelector((state) => state.user)
const dispatch = useDispatch();
useEffect(() => {
if(!user.loading){
setFirstName('');
setLastName('');
setEmail('');
setPassword('');
setProfilePicture('')
}
}, [user.loading]);
const userSignup = (e) => {
e.preventDefault();
const user = {
firstName,
lastName,
email,
password,
profilePicture
};
dispatch(signup(user));
console.log(user)
};
if(auth.authenticate) {
return <Redirect to={'/'} />
}
if(user.loading) {
return <p>Loading...</p>
}
const handleProfilePicture = (e) => {
setProfilePicture(e.target.files[0]);
}
return (
<Layout>
<Container>
{user.message}
<Row>
<Col md={{ span:6, offset:3 }}>
<Form onSubmit={userSignup}>
<Row>
<Col md = {6}>
<Input
label = 'First Name'
placeholder='First Name'
value= {firstName}
type='text'
onChange={(e) => setFirstName(e.target.value)}
/>
</Col>
<Col md = {6}>
<Input
label = 'Last Name'
placeholder='Last Name'
value= {lastName}
type='text'
onChange={(e) => setLastName(e.target.value)}
/>
</Col>
</Row>
<Input
label='Email'
placeholder='Email'
value={email}
type='email'
onChange={(e) => setEmail(e.target.value)}
/>
<Input
label='Password'
placeholder='Password'
value={password}
type='password'
onChange={(e) => setPassword(e.target.value)}
/>
<input type="file" name= 'profilePicture' onChange={handleProfilePicture} />
<Button variant='primary' type='submit' >
Submit
</Button>
</Form>
</Col>
</Row>
</Container>
</Layout>
)
}
export default Signup
To send files along with other data from the frontend, you need to use the FormData API. With a user object and axios, I'd do something like this:
const formData = new FormData();
// Append all properties of the `user` object to the form
for (const [key, value] of Object.entries(user)) {
formData.append(key, value);
}
const response = await axios.post('/signup', formData, {
headers: {
// Multer only parses "multipart/form-data" requests
'Content-Type': 'multipart/form-data',
},
});

Create a user document after creating a user in authentication in firebase

I'm trying to figure out how to create a user document in firestore after a user auth record is created.
My current attempt is below.
When i add the async/await the code generates error messages. When I remove them, the authentication part works to create a user record in the authentication part of firebase, but the firestore record is not created. No error message is generated.
Can anyone see what's going wrong?
import React, {useState} from 'react';
import { auth, firestore } from '../../../services/firebase/firebase';
import { useHistory } from 'react-router-dom';
import Button from '#material-ui/core/Button';
import TextField from '#material-ui/core/TextField';
import Dialog from '#material-ui/core/Dialog';
import DialogActions from '#material-ui/core/DialogActions';
import DialogContent from '#material-ui/core/DialogContent';
import DialogContentText from '#material-ui/core/DialogContentText';
import DialogTitle from '#material-ui/core/DialogTitle';
import { Buttons } from '../navigation/styles';
export default function FormDialog() {
const [open, setOpen] = React.useState(false);
let [loading, setLoading] = useState(false);
const history = useHistory();
const [displayName, setDisplayName ] = useState('');
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [error, setError] = useState(null);
const handleClickOpen = () => {
setOpen(true);
};
const handleClose = () => {
setOpen(false);
};
const handleSubmit = async (event) => {
setLoading(true);
event.preventDefault();
auth.createUserWithEmailAndPassword( email, password)
THE ATTEMPT BELOW LOGS THE UID, BUT SAYS TypeError: user.updateProfile
is not a function
.then((res) => {
console.log("logging user", (auth.currentUser.uid) );
const user = auth.currentUser.uid;
return user.updateProfile({
displayName: displayName
})
firestore.collection('users').doc(auth.currentUser.uid)
.set({
fullName: displayName,
createdAt: firestore.fieldValue.serverTimestamp(),
})
})
THIS IS ANOTHER ATTEMPT, WHICH ASLO DOESNT WORK TO MAKE THE USER
DOCUMENT IN FIRESTORE
.then(() => {
if (auth.currentUser != null) {
auth.currentUser.updateProfile({
displayName: displayName
})
firestore.collection('users').doc(auth.currentUser.uid)
.set({
fullName: displayName,
createdAt: firestore.fieldValue.serverTimestamp(),
})
}
})
//THIS IS ANOTHER ATTEMPT, IN THE ALTERNATIVE TO THE ABOVE, WHICH ALSO
DOESNT WORK
.then((res) => {
const user = auth.currentUser;
return user.updateProfile({
displayName: displayName
})
firestore.collection('users').doc(auth.currentUser.uid)
.set({
fullName: displayName,
createdAt: firestore.fieldValue.serverTimestamp(),
})
})
.then(() => {
history.push("/");
})
.catch(error => {
console.error(error);
})
.then(() => {
clear();
})
.then(() => {
handleClose()
})
.finally(() => {
setLoading(false);
});
};
THIS IS A FURTHER ATTEMPT, WHICH I CAN'T TEST BECAUSE SOMETHING ABOUT THE THEN STATEMENT THAT TRIES TO PUSH HISTORY IS CONSIDERED TO HAVE A PARSING ERROR. I CAN'T FIND ANY TUTORIALS ABOUT HOW TO FIGURE OUT SOLVING THOSE.
const createUserDocument = async (user, displayName) => {
if (!user) return;
const userRef = firestore.doc(`users/${user.uid}`);
const snapshot = await userRef.get();
if (!snapshot.exists) {
const { email } = user;
const { displayName } = displayName;
try {
await userRef.set({
displayName,
email,
createdAt: new Date(),
});
} catch (error) {
console.log('Error in creating user', error);
}
}
};
const handleSubmit = async (event) => {
setLoading(true);
event.preventDefault();
try {
const { user } = await auth.createUserWithEmailAndPassword(
email,
password
);
await createUserDocument(user, { displayName });
} catch (error) {
console.log('error', error);
}
.then(() => {
history.push("/");
})
.then(() => {
clear();
})
.then(() => {
handleClose()
})
.finally(() => {
setLoading(false);
});
};
//continuing after all current attempts
const onChangeHandler = event => {
const { name, value } = event.currentTarget;
if (name === "userEmail") {
setEmail(value);
} else if (name === "userPassword") {
setPassword(value);
} else if (name === "displayName") {
setDisplayName(value);
}
};
const clear = () => {
setDisplayName("");
setEmail("");
setPassword("");
};
return (
<div>
<Buttons onClick={handleClickOpen}>
Join
</Buttons>
<Dialog open={open} onClose={handleClose} aria-labelledby="form-dialog-title">
<DialogTitle id="form-dialog-title">Join the waitlist</DialogTitle>
<DialogContent>
<DialogContentText>
Join
</DialogContentText>
<TextField
autoFocus
margin="dense"
label="Full name"
type="text"
fullWidth
name="displayName"
value={displayName}
placeholder="Jill Green"
id="displayName"
onChange={event => onChangeHandler(event)}
/>
<TextField
margin="dense"
label="Email Address"
type="email"
fullWidth
name="userEmail"
value={email}
placeholder="email address"
id="userEmail"
onChange={event => onChangeHandler(event)}
/>
<TextField
margin="dense"
label="Password"
type="password"
fullWidth
name="userPassword"
value={password}
id="userPassword"
placeholder="Minimum 6 characters"
onChange={event => onChangeHandler(event)}
/>
</DialogContent>
<DialogActions>
<Button onClick={handleClose} color="primary">
Cancel
</Button>
<Button
onClick={handleSubmit}
color="primary">
Register
</Button>
</DialogActions>
</Dialog>
</div>
);
}
Apart from ongoing issues trying to figure out how to record timestamps in firebase, this works to create the user document record.
const handleSubmit = async (event) => {
setLoading(true);
event.preventDefault();
auth.createUserWithEmailAndPassword(
email,
password
)
.then(credential => {
if (credential && credential.user) {
firestore.collection("users")
.doc(credential.user.uid)
.set({
email: email,
displayName: displayName,
// createdAt: firestore.Timestamp.now()
// createdAt: firestore.fieldValue.serverTimestamp()
// createdAt: firebase.firestore.fieldValue.serverTimestamp()
});
history.push("/");
}
})

How to handle error and success response the cleanest way?

How would I show a server response error or success message the cleanest way ?
Right now, I'm using an async function to make an axios request, and on success/error im just updating a local state (with react-hook-form), but I feel like it's "ugly" and I want the pages to be as clean as possible and put the code to handle success and error messages in the service request, behind the scenes.
Example :
ForgotPassword.jsx
import React, { useState } from 'react';
import Layout from '../components/core/Layout';
import axios from 'axios';
import { useForm } from 'react-hook-form';
import { Button, Form, Alert } from 'react-bootstrap';
import { regex } from '../constants';
import { isAuth } from '../helpers';
import { forgotPassword } from '../services/User';
import { Redirect } from 'react-router-dom';
const Forgot = () => {
const {
handleSubmit,
register,
errors,
getValues,
setError,
setValue,
clearError
} = useForm({
mode: 'onBlur'
});
register({ name: 'responseError' });
register({ name: 'responseSuccess' });
const { responseSuccess } = getValues();
const onSubmit = async values => {
try {
const response = await forgotPassword(values);
setValue([{ responseSuccess: response.data.message }]);
// set response success msg to local state responseSuccess
} catch (error) {
setError('responseError', '', error);
// set response error msg to local state responseError
}
};
const forgotPasswordForm = () => (
<>
<Form onSubmit={handleSubmit(onSubmit)}>
<Form.Group>
<Form.Label>Email address</Form.Label>
<Form.Control
name='email'
ref={register({
required: true,
pattern: {
value: regex.email,
message: 'Invalid email address'
}
})}
type='email'
placeholder='Enter email'
isInvalid={errors.email}
/>
<Form.Control.Feedback type={errors.email ? 'invalid' : 'valid'}>
{errors.email && errors.email.message}
</Form.Control.Feedback>
</Form.Group>
<Button variant='primary' type='submit'>
Submit
</Button>
</Form>
<br />
{errors.responseError && (
<Alert
variant='danger'
dismissible
onClose={() => clearError('responseError')}>
{errors.responseError.message}
</Alert>
)}
</>
);
const forgotPasswordFormSuccess = () => (
<Alert
variant='success'
className='mt-5'
dismissible
onClose={() => setValue([{ responseSuccess: '' }])}>
{responseSuccess}
</Alert>
);
if (isAuth()) return <Redirect to='/' />;
return (
<Layout>
<div>
<h1>Forgot password</h1>
{responseSuccess ? forgotPasswordFormSuccess() : forgotPasswordForm()}
</div>
</Layout>
);
};
export default Forgot;
forgotPassword Function
export const forgotPassword = async ({ email }) => {
return new Promise(async (resolve, reject) => {
try {
const response = await Axios({
method: 'PUT',
url: `${process.env.REACT_APP_API}/forgot-password`,
data: { email }
});
resolve(response);
} catch (error) {
if (error.response) {
reject(error.response && error.response.data.error);
}
reject('Something went wrong. please try again later.');
}
});
};
Hope this is what you want
export const forgotPassword = ({ email }) => {
return new Promise((resolve, reject) => {
axios(`${process.env.REACT_APP_API}/forgot-password`, {
method: 'PUT',
data: { email }
})
.then(res => resolve(res.data))
.catch(err => reject(err))
});
};
const onSubmit = values => {
forgotPassword(values)
.then(res => setValue([{ responseSuccess: res.message }]))
.catch(err => setError('responseError', '', err));
};

Resources