When should I call custom hook not breaking any rules of hooks? - javascript

I do have a simple component with form. I want to use useSendEmail hook which returns the server response (success or failure). Where do I call this hook so it doesn't fire on the first render but only after I get my data from the user and save it in the state?
Expected behaviour: hook useSendEmail is called when the email object contains users input and returns status (if sent successfully or not).
I am aware I need to call hook at the top level in the component, but I do I wait for the data from input fields?
Actual behaviour: I am breaking the rule of hooks.
// importing everything I need here
const ContactPage = () => {
const initial = {
from: '',
message: '',
email: '',
};
const [formData, setFormData] = useState(initial);
const [email, setEmail] = useState(null);
const handleChange = ({ name, value }) => {
setFormData({ ...formData, [name]: value });
};
useEffect(() => {
if (email === null) return;
const response = useSendEmail(email);
}, [email]);
const handleSubmit = (e) => {
e.preventDefault();
setEmail(formData);
};
return (
<DefaultLayout title="Contact">
<StyledContainer>
<form className="contact_form" onSubmit={(e) => handleSubmit(e)}>
<input
name="from"
type="text"
value={formData.from}
onChange={(e) => handleChange(e.target)}
placeholder="Your full name"
/>
<textarea
name="message"
value={formData.message}
onChange={(e) => handleChange(e.target)}
placeholder="Your Message"
/>
<input
name="email"
type="email"
value={formData.email}
onChange={(e) => handleChange(e.target)}
placeholder="Your e-mail"
/>
<button type="submit">SUBMIT</button>
</form>
</StyledContainer>
</DefaultLayout>
);
};
export default ContactPage;
EDIT:
this is how my hook looks like after refactoring with your suggestions. I am now importing the hook and the method in the top level component and everything seems to work perfectly.
import { useState } from 'react';
import emailjs from 'emailjs-com';
import { userID, templateID, serviceID } from '../data/account';
const useSendEmail = (email) => {
const [response, setResponse] = useState(null);
const successMsg = 'Your message has been successfully sent';
const errorMsg = 'Your message has not been sent. Try again.';
const sendEmail = async () => emailjs
.send(serviceID, templateID, email, userID)
.then(
(res) => {
if (res.status === 200) {
setResponse(successMsg);
}
if (res.status !== 200) {
setResponse(errorMsg);
}
},
(err) => {
console.log(err);
},
);
return { response, sendEmail }
};
export default useSendEmail;

Related

i have to click on the button twice to post data to api in react

I have console.log(ed) the values while executing and what happens is on the first click, inputValue is sent with a null string to api, then on the next click the inputValue with string is sent to api. I have already changed the value of inputValue using the setter function in input tag with onChange function and then i have called the api so How do i fix it so it sends it on the first click.
const InputEmail = () => {
const navigate = useNavigate()
const [inputValue, setInputValue] = useState('')
const [apiData, setApiData] = useState('')
const [isError, setIsError] = useState(false)
// useEffect(() => {
// //API()
// }, [])
const API = () => {
console.log(inputValue)
axios
.post(url, {
email: inputValue
})
.then((response) => {
setApiData(response.data)
})
console.log(apiData.is_active)
}
const handleSubmit = () => {
API()
if(apiData.is_active) {
localStorage.setItem('email',inputValue)
navigate("/assessment")
} else {
setIsError(true)
}
}
return (
<div className='main'>
<FormControl>
<h2 className='text'>Registered Email address</h2>
<Input id='email' type='email' value={inputValue} onChange={e => setInputValue(e.target.value)}/>
{
isError ? <FormHelperText color='red'>Email not found. Please enter registered email</FormHelperText> : null
}
<Button
mt={4}
colorScheme='teal'
type='submit'
onClick={handleSubmit}
>
Submit
</Button>
</FormControl>
</div>
)
}
You must wait for your axios to fetch data from the url before making a handle. It will work if you await untill your async API() functions brings data.
const API = () => {
return axios.post(url, {
email: inputValue,
});
};
const handleSubmit = async () => {
const response = await API();
if (response.data.is_active) {
localStorage.setItem("email", inputValue);
navigate("/assessment");
} else {
setIsError(true);
}
};

Uncaught Error on using Axios PUT in React "Uncaught (in promise) Error status 400"

I'm having an error while updating a record in my API using Axios.Put, and i can't seem to find solution on this problem and also i'm not able to understand it fully on what does it want to tell me.
This is my update component where i use PUT to update a record.
import React, { useState, useEffect } from 'react';
import axios from 'axios';
export default function Update() {
const [ProductDescription, setProductDescription] = useState('');
const [ProductCount, setProductCount] = useState('');
const [id, setID] = useState(null);
useEffect(() => {
setID(localStorage.getItem('productID'))
setProductDescription(localStorage.getItem('productDescription'));
setProductCount(localStorage.getItem('productCount'));
}, []);
const updateAPIData = () => {
axios.put(`https://localhost:44380/api/products/`+id, {
ProductDescription,
ProductCount
}).then(response => {
if(response.data !=null) {
alert("Update Successfully")
}
})
}
return (
<div>
<Form className="create-form">
<Form.Field>
<label>Product Description </label>
<input placeholder='Product Description' value={ProductDescription} onChange={(e) => setProductDescription(e.target.value)}/>
</Form.Field>
<Form.Field>
<label>Product Count</label>
<input placeholder='Product Count' value={ProductCount} onChange={(e) => setProductCount(e.target.value)}/>
</Form.Field>
<Button type='submit' onClick={updateAPIData}>Update</Button>
</Form>
</div>
)
};
I have also used this code but still having the same error
axios.put(`https://localhost:44380/api/products/${id}'
And this is how i get the data on my other component using a button
const [ProductDescription, setProductDescription] = useState('');
const [ProductCount, setProductCount] = useState('');
const [id, setID] = useState(null);
useEffect(() => {
setID(localStorage.getItem('productID'))
setProductDescription(localStorage.getItem('productDescription'));
setProductCount(localStorage.getItem('productCount'));
}, []);
const setData = (data) => {
let { productID, productDescription, productCount } = data;
localStorage.setItem('productID', productID);
localStorage.setItem('productDescription', productDescription);
localStorage.setItem('productCount', productCount);
<IconButton><Edit onClick={() => setData(data)}/></IconButton>
I do get undefined data using the localStorage, why could that be?

How to input an existing value into an input field in React for an edit form

I have an existing Task with a title and a description, and I want to navigate to an edit form. By default I want the existing title and description values to populate the inputs using React. The important piece of this code I'm asking about is value={task.title}. Please ignore how the data is being pulled in (I'm new to React and I'm experimenting). The onChange and onSubmit handles work correctly, but the error obviously indicates I'm doing it wrong and it does cause occasional bugs.
I've tried abstracting those values into some sort of formValues state as well, but no matter how the values are being input, if the value={howeverIDoIt} is being directly manipulated I get the error.
import React, { useEffect, useState } from 'react';
import { HEADERS, TODO_URL } from '../urls';
import { useHistory, useParams } from 'react-router-dom';
const TaskEdit = () => {
const [task, setTask] = useState({});
const { id } = useParams();
const history = useHistory();
useEffect(() => {
fetch(`${TODO_URL}/api/tasks/${id}/`, {headers: HEADERS})
.then(response => response.json())
.then(responseJson => {
setTask(responseJson);
});
}, []);
const handleChange = (e) => {
setTask(e.target.value)
}
const handleSubmit = (e) => {
e.preventDefault();
const body = {
'title': e.target.form[0].value,
'description': e.target.form[1].value
}
fetch(
`${TODO_URL}/api/tasks/${id}/edit/`,
{
headers: HEADERS,
method: 'PUT',
body: JSON.stringify(body)
}
).then(res => res).catch(err => err);
history.push(`/task/${id}`)
}
return (
<form>
<div>
<label>Title</label>
<input type="text" onChange={handleChange} value={task.title} />
</div>
<div>
<label>Description</label>
<textarea onChange={handleChange} value={task.description}></textarea>
</div>
<button onClick={handleSubmit}>Submit</button>
</form>
);
}
export default TaskEdit;
I have tried putting in a default value for useState like so: useState({title: 'title', description: 'description'}) but that doesn't prevent the error, nor does adding this edit form into the Task.js component, where task is most definitely defined.
You have:
<input type="text" onChange={handleChange} value={task.title} />
Your handleChange method is:
const handleChange = (e) => {
setTask(e.target.value)
}
When your onChange fires, your task state will be set to a String (the value of <input />)
So when you are referencing task.title after your onChange fires, it will be undefined. The same is true for task.description.
Try this:
const handleTitleChange = (e) => {
setTask({...task, title: e.target.value})
}
const handleDescriptionChange = (e) => {
setTask({...task, description: e.target.value})
}
<input type="text" onChange={handleTitleChange} value={task.title} />
<textarea onChange={handleDescriptionChange} value={task.description} />
Alternatively, you could split up the task state to title and description, respectively.

How do i get the value of text input field using react

I'm creating a register form in react with validation. The values i'm asking is Username, Email, password + (controll password). Everything about the form works, validations, Errors and if you click sign up you go to a new page. Now i want to extract the values to a my MySql database. I succeed in putting stuff in my database so the link works but i can't get the values of what i typed in the form.
I have tried
onChange={(e) => {
setUsernameReg(e.target.value);
}}
(see commented item)
But when i tried this I couldn't fill anything in Username. The code for the other inputs (email, password) is the same apart from the names.
So in short I want to get the value what you typed in a textbox to my database.
Code: FormSignup.js
import React, { useEffect, useState } from 'react';
import Axios from 'axios';
import validate from './validateInfo';
import useForm from './useForm';
import './Form.css';
const FormSignup = ({ submitForm }) => {
const { handleChange, handleSubmit, values, errors } = useForm(
submitForm,
validate
);
const [usernameReg, setUsernameReg] = useState("");
const [emailReg, setEmailReg] = useState("");
const [passwordReg, setPasswordReg] = useState("");
Axios.defaults.withCredentials = true;
const register = () => {
Axios.post("http://localhost:3001/register", {
username: usernameReg,
password: passwordReg,
email: emailReg,
}).then((response) => {
console.log(response);
});
};
return (
<div className='form-content-right'>
<form onSubmit={handleSubmit} className='form' noValidate>
<h1>
Get started with us today! Create your account by filling out the
information below.
</h1>
<div className='form-inputs'>
<label className='form-label'>Username</label>
<input
className='form-input'
type='text'
name='username'
placeholder='Enter your username'
value={values.username}
onChange={handleChange}
/*
//onChange={(e) => {
// setUsernameReg(e.target.value);
//}}
*/
/>
Code UseForm.js
import { useState, useEffect } from 'react';
import Axios from 'axios';
const useForm = (callback, validate) => {
const [values, setValues] = useState({
username: '',
email: '',
password: '',
password2: ''
});
const [errors, setErrors] = useState({});
const [isSubmitting, setIsSubmitting] = useState(false);
const [usernameReg, setUsernameReg] = useState("");
const [emailReg, setEmailReg] = useState("");
const [passwordReg, setPasswordReg] = useState("");
Axios.defaults.withCredentials = true;
const register = () => {
Axios.post("http://localhost:3001/register", {
username: usernameReg,
password: passwordReg,
email: emailReg,
}).then((response) => {
console.log(response);
});
};
const handleChange = e => {
const { name, value } = e.target;
setValues({
...values,
[name]: value
});
};
const handleSubmit = e => {
e.preventDefault();
setErrors(validate(values));
setIsSubmitting(true);
};
useEffect(
() => {
if (Object.keys(errors).length === 0 && isSubmitting) {
callback();
}
},
[errors]
);
return { handleChange, handleSubmit, values, errors };
};
export default useForm;
The code is from https://www.youtube.com/watch?v=KGFG-yQD7Dw&t and https://www.youtube.com/watch?v=W-sZo6Gtx_E&t
By using the value prop of the input, you turn it into a controled input element and thus need to update its value via a state variable. So this:
<input
className='form-input'
type='text'
name='username'
placeholder='Enter your username'
value={values.username}
onChange={handleChange}
/*
//onChange={(e) => {
// setUsernameReg(e.target.value);
//}}
*/
/>
Should just be this:
<input
className='form-input'
type='text'
name='username'
placeholder='Enter your username'
value={usernameReg}
onChange={e => setUsernameReg(e.target.value)}
/>
Note that this only answers this part:
I succeed in putting stuff in my database so the link works but i can't get the values of what i typed in the form
So this is how you can access those values. I can't guide you on how to get those values all the way to your DB as there is a longer distance they have to travel and I don't know what else could be in the way.
You should also look into useRef(), which will give you access to those input fields without updating your state on every change of the input and thus re-rendering your form over and over.
You can do something like this:
...
const regInput = React.useRef();
...
...
<input
ref={regInput}
className='form-input'
type='text'
name='username'
placeholder='Enter your username'
Then when you're ready to submit, just access the value of the username input like so:
...
const v = regInput.current.value;
...

The code in the then method is not executed and error handling is performed

I'm developing a web app in React and firebase and I'm having trouble getting it to work.
Here is my code
import React, { useRef, useState } from "react"
import { Form, Button, Card, Alert } from "react-bootstrap"
import { useAuth } from "../contexts/AuthContext"
import { Link, useHistory, Redirect, Route } from "react-router-dom"
import { db } from "../firebase"
import Dashboard from "../components/Dashboard"
export default function UpdateProfile() {
const usernameRef = useRef()
const emailRef = useRef()
const passwordRef = useRef()
const passwordConfirmRef = useRef()
const { updateUser, currentUser, updatePassword } = useAuth()
const [error, setError] = useState("")
const [loading, setLoading] = useState(false)
const history = useHistory()
function handleSubmit(e) {
e.preventDefault()
if (passwordRef.current.value !== passwordConfirmRef.current.value) {
return setError("Passwords do not match")
}
if (passwordRef.current.value) {
updatePassword(passwordRef.current.value)
}
const uid = currentUser.uid
db.collection('users').doc(uid).get()
.then(snapshot => {
const data = snapshot.data()
try {
setLoading(true)
setError("")
updateUser(usernameRef.current.value, emailRef.current.value, data)
history.push('/dashboard')
} catch {
setError("Failed to update account")
}
setLoading(false)
})
}
return (
<>
<Card>
<Card.Body>
<h2 className="text-center mb-4">Update Profile</h2>
{error && <Alert variant="danger">{error}</Alert>}
<Form onSubmit={handleSubmit}>
<Form.Group id="username">
<Form.Label>Name</Form.Label>
<Form.Control
type="text"
ref={usernameRef}
required
defaultValue={currentUser.username}
/>
</Form.Group>
<Form.Group id="email">
<Form.Label>Email</Form.Label>
<Form.Control
type="email"
ref={emailRef}
required
defaultValue={currentUser.email}
/>
</Form.Group>
<Form.Group id="password">
<Form.Label>Password</Form.Label>
<Form.Control
type="password"
ref={passwordRef}
placeholder="Leave blank to keep the same"
/>
</Form.Group>
<Form.Group id="password-confirm">
<Form.Label>Password Confirmation</Form.Label>
<Form.Control
type="password"
ref={passwordConfirmRef}
placeholder="Leave blank to keep the same"
/>
</Form.Group>
<Button disabled={loading} className="w-100" type="submit">
Update
</Button>
</Form>
</Card.Body>
</Card>
<div className="w-100 text-center mt-2">
<Link to="/">Cancel</Link>
</div>
</>
)
}
This is the code for the user's edit function: enter a value in the form and press the button to run handleSubmit.
function updateUser(username, email, data) {
const uid = data.uid
db.collection('users').doc(uid).set({
email: email,
username: username,
}, {merge: true})
}
useEffect(() => {
const unsubscribe = auth.onAuthStateChanged(async(user) => {
if (user) {
const uid = user.uid
console.log(uid)
await db.collection('users').doc(uid).get()
.then(snapshot => {
const data = snapshot.data()
setCurrentUser(data)
setLoading(false)
})
}
})
return unsubscribe
}, [])
And the following code rewrites the firestore data After this updateUser function is executed, we want to do a history.push in then of handleSubmit to redirect to /dashboard, but we want to get the console to say "success! in the console and the "failure! in the console and the message "success!" appears on the app.
When I looked at the firestore data, I found that the values I entered were properly reflected in the firestore.
This tells me that the then part of handleSubmit is not working, but I don't know why it's not working.
If you have a solution, I'd love to hear it.
Thank you.
You need to
Either await or put a .catch onto the updateUser Promise chain (the try/catch around it will only catch async errors if the Promise is awaited)
Return the Promise from updateUser
Pass a function to the .then callback - your .then(console.log("success!!")) invokes console.log immediately and passes undefined to the .then
function handleSubmit(e) {
e.preventDefault()
if (passwordRef.current.value !== passwordConfirmRef.current.value) {
return setError("Passwords do not match")
}
if (passwordRef.current.value) {
updatePassword(passwordRef.current.value)
}
const uid = currentUser.uid
db.collection('users').doc(uid).get()
.then(snapshot => {
const data = snapshot.data()
setLoading(true)
setError("")
return updateUser(usernameRef.current.value, emailRef.current.value, data);
})
.then(() => {
// Success
history.push('/dashboard')
})
.catch((error) => {
setError("failed!!")
})
.finally(() => {
setLoading(false)
});
}
function updateUser(username, email, data) {
const uid = data.uid
return db.collection('users').doc(uid).set({
email: email,
username: username,
}, { merge: true })
.then(() => console.log("success!!"))
}

Categories

Resources