Resetting initial state of useState - javascript

In order to improve my React skills, I have been trying to build a reusable form state hook and form validator. My custom hook, FormState, initializes an object with empty strings for values to be used as an initial state for my hook. I wrote a function, clearInputs, that I expected to reset my inputs to their initial state but it is failing to update.
I've poked around looking for an answer, even referring to this Stack Overflow post: Reset to Initial State with React Hooks . Still no dice.
// MY FORMSTATE HOOK
import { useState } from 'react';
const FormState = props => {
let initialState = { ...props };
const [ inputs, setInputs ] = useState(initialState);
const [ errors, setErrors ] = useState(initialState);
const handleInput = e =>
setInputs({
...inputs,
[e.target.name]: e.target.value
});
const handleError = errs => setErrors({ ...errors, ...errs });
const resetForm = () => {
setInputs({ ...initialState });
setErrors({ ...initialState });
};
const clearInputs = () => {
console.log('SUPPOSED TO CLEAR', initialState)
console.log('MY INPUTS', inputs)
setInputs({ ...initialState });
console.log('AFTER THE SETTING', inputs)
};
return [ inputs, handleInput, errors, handleError, resetForm, clearInputs ];
};
export default FormState;
// REGISTER FORM
import React, { useEffect } from 'react';
import { connect } from 'react-redux';
import FormState from './formState';
import Field from './field';
import { registerUser } from '../../actions/users';
import './forms.css';
const RegisterForm = props => {
const isLoggedIn = localStorage.getItem('user');
useEffect(
() => {
if (isLoggedIn) {
const parsedUser = JSON.parse(isLoggedIn);
props.history.push(`/profile/${parsedUser.pk}`);
}
},
[ isLoggedIn ]
);
const initialInputs = {
username: '',
password1: '',
password2: '',
first_name: '',
last_name: ''
};
const [ inputs, handleInput, errors, handleErrors, resetForm, clearInputs ] = FormState(initialInputs);
const handleSubmit = e => {
e.preventDefault();
const validForm = validate(inputs, handleErrors);
if (validForm) {
props.registerUser(inputs);
resetForm();
}
else {
clearInputs();
}
};
return (
<div className='form-wrap'>
<h1>Register Here</h1>
<form className='form' onSubmit={handleSubmit}>
<Field
label='Username'
fieldname='username'
value={inputs.username}
placeholder='Enter Your Username'
fielderror={errors.username}
handleInput={handleInput}
classNames='form-section'
/>
<Field
label='Password'
fieldname='password1'
value={inputs.password1}
placeholder='Enter Your Password'
fielderror={errors.password1}
handleInput={handleInput}
classNames='form-section'
/>
<Field
label='Confirm Password'
fieldname='password2'
value={inputs.password2}
placeholder='Confirm Your Password'
fielderror={errors.password2}
handleInput={handleInput}
classNames='form-section'
/>
<Field
label='First Name'
fieldname='first_name'
value={inputs.first_name}
placeholder='Enter Your First Name'
fielderror={errors.first_name}
handleInput={handleInput}
classNames='form-section'
/>
<Field
label='Last Name'
fieldname='last_name'
value={inputs.last_name}
placeholder='Enter Your Last Name'
fielderror={errors.last_name}
handleInput={handleInput}
classNames='form-section'
/>
<button type='submit' className='submit-button'>
Submit
</button>
</form>
</div>
);
};
const validate = (inputs, handleErrors) => {
let errs = {};
const { username, password1, password2, first_name, last_name } = inputs;
if (!username) {
errs.username = 'Username is missing';
}
if (!password1) {
errs.password1 = 'Password is missing';
}
if (!password2) {
errs.password2 = 'Confirm password is missing';
}
if (!first_name) {
errs.first_name = 'First name is required';
}
if (!last_name) {
errs.last_name = 'Last name is required';
}
if (username.length < 6) {
errs.username = 'Username is too short';
}
if (password1.length < 8) {
errs.password1 = 'Password is too short';
}
if (password1 !== password2) {
errs.password1 = 'Passwords must match';
errs.password2 = 'Passwords must match';
}
if (Object.keys(errs).length) {
handleErrors(errs);
return false;
}
else {
return true;
}
};
const mapStateToProps = state => {
return {
loggedInUser: state.users.loggedInUser,
registerPending: state.users.registerPending,
registerError: state.users.registerError
};
};
const mapDispatchToProps = dispatch => {
return {
registerUser: newUser => {
dispatch(registerUser(newUser));
}
};
};
export default connect(mapStateToProps, mapDispatchToProps)(RegisterForm);
When clearInputs is triggered, it should reset inputs to the initial state. Instead nothing is happening. Any help is really appreciated.
EDIT:
Let me further clarify. Each Field in my form is passed a value from inputs (username, password1, etc). When clearInputs is called, it clears the inputs in the hook but it DOES NOT clear the values in the Field.

Your clearInputs function is working as intended. The setInputs function returned by useState is asynchronous, causing your 'AFTER THE SETTING' console log to show the value of inputs before it has been updated.
Basic use of your custom hook is shown to work here.. https://codesandbox.io/s/jznpk7w85w
btw, you should prefix your custom hook name with use.. https://reactjs.org/docs/hooks-custom.html#extracting-a-custom-hook

About why you log isn't right, one must understand this: Everytime your hook is called (basically on every form render) there is a new clearInputs function created, that has its own version of inputs. So, within the clearInputs function itself, inputs cannot change, because they come from higher up in the scope, the useState.
If you want to notice changes between 2 calls of your hook, you could log inputs just before returning [inputs, ...].
Again, in your hook, you are not calling setInputs, you are defining a clearInputs function that will trigger a state change, that will re-render your component, which will use your hook once again, your hook will read the new value of inputs, and create a new clearInputs function.

Related

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

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;

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

TypeError: Cannot read property 'value' of undefined in REACTJS

I am getting this error on handleChange. I want to know what would be the issue??
const [testState, setTestState] = useState({
activeStep:0,
firstName: '',
lastName: '',
email: '',
occupation: '',
city: '',
bio: ''
});
const { activeStep } = testState;
const { firstName, lastName, email, occupation, city, bio } = testState;
const values = { firstName, lastName, email, occupation, city, bio }
const handleChange = (e) => {
const value = e.target.value;
setTestState({
...testState,
[e.target.name]: value
});
};
const handleNext = () => {
debugger
const { activeStep } = testState;
setTestState({activeStep:activeStep+1});
};
const handleBack = () => {
debugger
const { activeStep } = testState;
setTestState({activeStep:activeStep-1});
};
I am using this in material ui Stepper, like this. I want to textfield data to be there when i click next or back button. I hope you are familiar with Material Ui Stepper, it will be real help.
function getStepContent(step) {
switch (step) {
case 0:
return (
<TransportationDetail
handleNext={handleNext}
propsTransportation={propsParent.propsRateDetailsData}
exhandleChange={(e) => exhandleChange(e)}
values={values}
/>
);
case 1:
return (
<Testing 1
handleNext={handleNext}
handleBack={handleBack}
exhandleChange={(e) => exhandleChange(e)}
values={values}
/>
);
case 2:
return (
<Testing 2
handleNext={handleNext}
handleBack={handleBack}
exhandleChange={(e) => exhandleChange(e)}
values={values}
/>
);
default:
return "No data available";
}
}
Now passing as props to <Testing 1 /> Component like this:
const { values, exhandleChange } = props;
and to the textfield
<TextField
fullWidthid="outlined-size-normal"
variant="outlined"
name="firstName"
onChange={exhandleChange()}
value={values.firstName}
/>
<TextField
fullWidthid="outlined-size-normal"
variant="outlined"
name="lastName"
onChange={exhandleChange()}
value={values.lastName}
/>
There are 3 vulnerable places where the testState might get corrupted.
in handleChange
in handleNext, or
in handleBack
Looking at the code snippet, I believe you just want to update the value of activeStep in testState by incrementing or decrementing the value by 1 in handleNext and handleBack. And for handleChange, you want to go to the selected index.
Here we need to appreciate the beauty of spread operator coz that is only going to solve our problem. For handleNext and handleBack you may follow this code snippet,
const handleBack = () => {
const { activeStep } = testState;
const tempState = {...testState}
tempState.activeStep = activeStep - 1
setTestState(tempState);
};
const handleNext = () => {
const { activeStep } = testState;
const tempState = {...testState}
tempState.activeStep = activeStep + 1
setTestState(tempState);
};
const handleChange = (e) => {
const tempState = {...testState}
tempState.activeStep = e
setTestState(tempState);
};
And pass the selectedIndex of the component to handleChange while calling the function.

How to correctly update state with useState and forms?

I'm wanted to write a simple login form and validate the input. For that I just have a username and password input as well as a button to submit the inputs. However the error state is only correctly updated after submitting the form twice.
const LoginForm = () => {
const [account, setAccount] = useState({ username: "", password: "" });
const [errors, setErrors] = useState({});
const validate = () => {
const newErrors = {};
if (account.username.trim() === "")
newErrors.username = "Username is required.";
if (account.password.trim() === "")
newErrors.password = "Password is required.";
return Object.entries(newErrors).length === 0 ? null : newErrors;
};
const handleSubmit = e => {
e.preventDefault();
setErrors(validate());
if (errors) return;
// Call server
console.log("Submitted");
};
const handleChange = ({ currentTarget: input }) => {
account[input.name] = input.value;
setAccount({ ...account });
};
return (
<div>
<h1>Login</h1>
<form onSubmit={handleSubmit}>
<Input
name="username"
value={account.username}
label="Username"
onChange={handleChange}
/>
<Input
name="password"
value={account.password}
label="Password"
onChange={handleChange}
/>
<button className="btn btn-primary">Login</button>
</form>
</div>
);
};
error should be set to null if both input fields are not empty otherwise it should be set to an object including a description of the errors.
At the time you first submit you are still referencing the old error state thus your code won't work. The errors variable will updated after the next render. You need to change your handle submit like this.
const handleSubmit = e => {
e.preventDefault();
const updatedErrors = validate();
// setErrors won't change the value of errors at this point but when the re-render
// happens the new errors variable will have your updates
setErrors(updatedErrors);
if (updatedErrors) return;
// Call server
console.log("Submitted");
};
Think about using the old setState. if you had the following:
const { errors } = this.state;
const updatedErrors = validate();
this.setState(updatedErrors);
// at this point errors !== newErrors

initialValues does not get update when updating the form

I have a form called Client which has a single form which handles both add and edit. Add is working but when editing the form(form
is populated with initialValues if it's and edit form), the initialValues does not get update. I mean, if I go to client A form and update the
field called client_name from 'abc' to 'xyz' then the client_name will be saved as 'xyz' in server but the initialValues does not get update
so if i again go to same form without refreshing the page and save the form without changing anything then client_name is saved with previous
value i. 'abc' because initialValues is not updated when updating the field.
Here is my code
import React, { Component } from 'react';
import { reduxForm, initialize } from 'redux-form';
const mapDispatchToProps = (dispatch, props) => ({
addClient: clientData => dispatch(addClient(clientData)),
editClient: clientData => dispatch(editClient(clientData)),
loadClient: () => dispatch(loadClient(props.match.params.company)),
resetClient: () => dispatch(resetClient()),
});
const mapStateToProps = createStructuredSelector({
initialValues: selectClient(),
});
class Client extends Component<propsCheck> {
state = {
client: initialState,
isLoading: false,
};
componentDidMount() {
if (this.props.match.params.company) {
this.props.loadClient();
}
}
componentWillReceiveProps(nextProps) {
if (this.props.match.params.company) {
this.props.loadClient();
} else {
this.props.resetClient();
}
}
handleChange = ({ target: { name, value } }) => {
this.setState({ client: { ...this.state.client, [name]: value } });
};
handleSubmit = event => {
event.preventDefault();
const { client } = this.state;
const { initialValues, addClient: add, editClient: edit } = this.props;
if (isEmpty(initialValues)) {
add(client);
} else {
const updatedClient = updatedValue(initialValues, client, 'id');
edit(updatedClient);
}
this.setState({ isLoading: true });
};
render() {
const { invalid, submitting, initialValues } = this.props;
return (
<ClientForm
loading={this.state.isLoading}
handleChange={this.handleChange}
handleSubmit={this.handleSubmit}
disabled={invalid || submitting}
type={initialValues && initialValues.id ? 'Edit' : 'Add'}
reset={this.props.reset}
history={this.props.history}
/>
);
}
}
const withReduxForm = reduxForm({
form: 'clientForm',
fields: requiredFields,
validate,
enableReinitialize: true,
})(Client);
export default connect(
mapStateToProps,
mapDispatchToProps,
)(withReduxForm);
Your initial values are being populated from redux state (selectClient in mapStateToProps) right? So, when you update/edit the client_name, are you changing the data in redux??

Categories

Resources