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.
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.
Share Improve this question edited Mar 28, 2019 at 17:47 Xavier Munroe asked Mar 27, 2019 at 22:58 Xavier MunroeXavier Munroe 411 gold badge1 silver badge3 bronze badges 1- When building a piece of code with so many things to use from (everything your hook returns [inputs, handleInput, ...]), I would remand returning an object instead of an array. It makes it really easier to grab, for instance clearInputs, without having to get ALL the values ing before. useState returns an array, but it is not a pre-requisite for all hooks. – Bear-Foot Commented Mar 28, 2019 at 8:47
2 Answers
Reset to default 1Your 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/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 e 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 ponent, which will use your hook once again, your hook will read the new value of inputs
, and create a new clearInputs
function.