HandleChange/setState stopped working after migrating to react useState hook - javascript

I had a component using classBased react, and it was working fine, I decided to switch to usestate and it stopepd working. Now the handlechange records the state in a random manner, but it doesnt work properly
My handlechange and the useState
const [state, setState] = useState({
email: "",
password: "",
wrongCombo: false,
serverError: false
})
const handleChange = (evt) => {
const target = evt.target;
const value = target.value;
const name = target.name;
console.log(name)
console.log(value)
setState({
[name]: value
});
}
My handlesubmit (it detects random values in that console.log, icant find the logic, but the log wont get both values as per inputed in the handlechange)
const handleSubmit = (event) => {
event.preventDefault();
const { email, password } = state;
console.log(state)
props.login(email, password, "login").
then(data => {
}).catch((err) => {
if (err == "Error: Request failed with status code 403") {
setState({
wrongCombo: true
}, () => {
})
} else if (err == "Network error") {
setState({
serverError: true
})
}
})
}
And this is my render
<div>
<form>
{state.wrongCombo ? <Alert variant="danger" dismissible onClose={handleDismiss}> Wrong email and password combination </Alert> : null}
{state.serverError ? <Alert variant="danger" dismissible onClose={handleDismiss}> Server Error </Alert> : null}
<div class="form-group">
<label for="exampleInputEmail1">Email address</label>
<input type="email" name="email" class="form-control" id="exampleInputEmail1" aria-describedby="emailHelp" placeholder="Enter email" onChange={handleChange} />
</div>
<div class="form-group">
<label for="exampleInputPassword1">Password</label>
<input type="password" name="password" class="form-control" id="exampleInputPassword1" placeholder="Password" onChange={handleChange} />
</div>
<div className="text-center buttonContainer">
<button type="submit" class="btn btn-primary buttonLogin" onClick={handleSubmit}>Submit</button>
</div>
</form>
</div>

From the docs on useState:
unlike this.setState in a class, updating a state variable always replaces it instead of merging it.
You must replace all values in a useState object when updating them. You are only providing an object to the updater with one of the keys, so only that key will be retained.
A simple pattern for doing this would be to spread the previous state before passing your update:
setState(prev => ({...prev, [newKey]: newVal }));

Related

How maltiple value using usestate localstorage in react?

I want to work using maltiple values in use state and crud in local storage use state like
const [Data,setData]=[[{
name:'luis',
pass:'1234',
//.......
}]
]
And it updates with the form
<input>
// .......
if value true Display take look at this example I try but I cannot do how to solve it
import './App.css';
import React, { useState,useEffect } from 'react';
function App() {
const [User, setUser] = useState([
{
Name:'',
Pass:'',
Email:'',
}
]);
const [storedUser,setstoredUser]=useState([])
const handle = () => {
localStorage.setItem(JSON.stringfy(...User))
setstoredUser(...User);
};
const remove = () => {
localStorage.removeItem();
};
return (
<div className="App">
<h1>Name of the user:</h1>
<input
placeholder="Name"
name='Name'
value={User.Name}
onChange={(e) => setUser({...User,[e.target.name]:[e.target.value]})}
/>
<h1>Password of the user:</h1>
<input
type="password"
name="Pass"
placeholder="Password"
value={User.Pass}
onChange={(e) => setUser({...User,[e.target.name]:[e.target.value]})}
/>
<h1>Email of the user:</h1>
<input
type="mail"
name="Email"
placeholder="Email"
value={User.Email}
onChange={(e) => setUser({...User,[e.target.name]:[e.target.value]})}
/>
<div>
<button onClick={handle}>Done</button>
</div>
{storedUser.Name && (
<div>
Name: <p>{localStorage.getItem('Name')}</p>
</div>
)}
{storedUser.Pass && (
<div>
Password: <p>{localStorage.getItem('Pass')}</p>
</div>
)}
{storedUser.Email && (
<div>
Password: <p>{localStorage.getItem('Email')}</p>
</div>
)}
<div>
<button onClick={remove}>Remove</button>
</div>
</div>
);
}
export default App;
Here I try how to do this formate I try to all data in state and stringify set this local storage. then remove and display I think explaine on detail
You're missing the key
localStorage.setItem("User",JSON.stringfy(...User))
If you want each key. Loop over they keys and values and set them. As stated by another user, your UserState is an array where it should just be an object
Object.entries(User).forEach(([key,value])=>{
localStorage.setItem(key,value)
})
You are doing a few things incorrectly here:
you are not providing a key for local storage
you don't need to spread objects directly inside setState
use the removeItem method to clear the user from localStorage
you are setting the user as an array to state not an object, this is unnecessary in the example you have
If the goal is to persist a single user between sessions via local storage. Then all you need to do is save the user to local storage when the form is submitted.
Then, when the component loads, check local storage for user data.
import { useEffect, useState } from 'react'
function App() {
const [User, setUser] = useState({
Name: '',
Pass: '',
Email: '',
})
const handle = () => {
const nextUser = JSON.stringify(User)
localStorage.setItem('user', nextUser)
}
const remove = () => {
localStorage.removeItem('user')
}
useEffect(() => {
const storedUser = localStorage.getItem('user')
if (storedUser) {
setUser(JSON.parse(storedUser))
}
}, [])
return (
<div className="App">
<h1>Name of the user:</h1>
<input
placeholder="Name"
name="Name"
value={User.Name}
onChange={(e) => setUser({ ...User, [e.target.name]: e.target.value })}
/>
<h1>Password of the user:</h1>
<input
type="password"
name="Pass"
placeholder="Password"
value={User.Pass}
onChange={(e) => setUser({ ...User, [e.target.name]: e.target.value })}
/>
<h1>Email of the user:</h1>
<input
type="mail"
name="Email"
placeholder="Email"
value={User.Email}
onChange={(e) => setUser({ ...User, [e.target.name]: e.target.value })}
/>
<div>
<button onClick={handle}>Done</button>
</div>
{User.Name && (
<div>
Name: <p>{User.Name}</p>
</div>
)}
{User.Pass && (
<div>
Password: <p>{User.Pass}</p>
</div>
)}
{User.Email && (
<div>
Password: <p>{User.Email}</p>
</div>
)}
<div>
<button onClick={remove}>Remove</button>
</div>
</div>
)
}
export default App
Since User already is an array of user objects I wouldn't spread them in your handle function. You also need to set a key if you're saving in localStorage. This is how to save your list of users under "users":
const handle = () => {
localStorage.setItem("users", JSON.stringfy(User));
setstoredUser(User);
};
Now when retrieving users from the localStorage you can make use of JSON.parse like this:
users = JSON.parse(localStorage.getItem("users") || "[]");
And for deletion you would need the key "users" here as well:
localStorage.removeItem("users");

Hidden password only works in one input box

I have this JS code that shows 2 input boxes that asks for a password: 1) Password 2) Confirm Password. However, the clickShowPassword() is only connected to Password.
[Output] [1]: https://i.stack.imgur.com/IZoa3.png
Here's my whole js file that is connected to an react application.
import React from "react";
function ShowHidePassword(){
const [values, setValues] = React.useState({
password: "",
passwordConf: "",
showPassword: true,
});
const clickShowPassword = (event) => {
setValues({ ...values, showPassword: !values.showPassword });
event.preventDefault();
};
const passwordChange = (prop) => (event) => { setValues({ ...values, [prop]: event.target.value }); };
const mouseDownPassword = (event) => { event.preventDefault(); };
return (
<div>
<input
type={values.showPassword ? "text" : "password"}
onChange={passwordChange("password")}
value={values.password} id="signup-password"
placeholder="PASSWORD"
/>
<input
type={values.showPassword ? "text" : "passwordConf"}
onChange={passwordChange("passwordConf")}
value={values.passwordConf} id="signup-password-confirm"
placeholder="CONFIRM PASSWORD"
/>
<br/>
<button className="hide-password2" onClick={clickShowPassword} onMouseDown={mouseDownPassword}>
{values.showPassword===false? <i className="bi bi-eye-slash"></i> :<i className="bi bi-eye"></i> } Show Password
</button>
</div>
);
};
export default ShowHidePassword;
In your second input you used passwordConf as an input type, I think this happened because u copied the first input and batch-replaced all "password" words with "passwordConf", happens to the best of us :)

my onChange event is not working on input elements

I have a component called Resume.js in which I define a function called handleChange which takes in an input paramater and based on that input value it changes my global state.
Resume.js:
import React from 'react'
import PersonalInfo from './PersonalInfo'
import { useState } from 'react'
const Resume = () => {
const [resumeStates, setResumeStates] = useState({
step: 1,
firstName: '',
lastName: '',
email: '',
phone: '',
address: '',
linkedIn: '',
jobTitle: '',
city: '',
employer: '',
startDate:'',
endDate: '',
responsibilities: ''
})
const nextStep = () => {
const {step} = resumeStates
setResumeStates({
step: step+1
})
}
const prevStep = () => {
const {step} = resumeStates
setResumeStates({
step: step-1
})
}
const handleChange = input => e => {
setResumeStates({[input]: e.target.value})
}
I am passing this handleChange function as props to a component called PersonalInfo.js as such:
return (
<PersonalInfo nextStep={nextStep} handleChange={handleChange} values={values}/>
)
In PersonalInfo.js I am using that prop as an onChange for my input fields:
import React from 'react'
const PersonalInfo = ({nextStep, handleChange, values}) => {
const continue_step = (e) => {
e.preventDefault()
nextStep()
}
return (
<div>
<div className="container-lg w-50 rounded p-3 mt-5 ">
<h2 className="mb-3">Personal Details</h2>
<form>
<div className="mb-3 row">
<div className="form-group">
<label htmlFor="fname" className="form-label">First Name:</label>
<input type="text" className="form-control" id="fname" value={values.firstName} onChange={() => handleChange('firstName')}/>
<div className={`text-danger fw-bold ${alert ? 'hidden': ''}`}>This is a required field</div>
</div>
</div>
<div className="mb-3 row">
<div className="form-group">
<label htmlFor="lname" className="form-label">Last Name:</label>
<input type="text" className="form-control" id="lname" value={values.lastName} onChange={() => handleChange('lastName')}/>
<div className={`text-danger fw-bold ${alert ? 'hidden': ''}`}>This is a required field</div>
</div>
</div>
But my onChange for my input fields is not working as I cannot type anything in my input as a results my states do not change.
I would appreciate any kind of help...
Thank you for your time and attention.
You're passing something called values to the component for the input values, but I don't see it defined anywhere. It looks like it the values should just be the state object, so pass that:
<PersonalInfo nextStep={nextStep} handleChange={handleChange} values={resumeStates}/>
^-- here --^
Additionally, you're removing all of your state values any time you set one:
setResumeStates({[input]: e.target.value})
Unlike the setState operation in older class-based components, the set operation in the useState hook does not determine the difference for you and only update the new parts. It replaces the state with the new state entirely. Make sure you keep the existing state when updating it:
setResumeStates({ ...resumeStates, [input]: e.target.value })
The same will need to be done for your other state updates as well.
And finally, you're not passing the actual change event to the change handler. Only the title. You can pass the change event to the function returned by the function of the change handler:
onChange={e => handleChange('firstName')(e)}
Or [potentially] more simply:
onChange={handleChange('firstName')}
This might be getting a little confusing though and is likely to result in bugs. (After all, it already has.) Instead of the function within a function, just accept both arguments in the handleChange function:
const handleChange = (input, e) => {
setResumeStates({...resumeStates, [input]: e.target.value})
}
And pass them both:
onChange={e => handleChange('firstName', e)}
As an aside, to help you with design-time validation of what's being passed to what functions, you might try making use of TypeScript in your React development.
Ok, you're doing a funception here.
const handleChange = input => e => {
setResumeStates({[input]: e.target.value})
}
This is a "function" that returns a "function that updates state". What you need is to bind a "function that updates state" to "onChange"
So you can try one of these.
First way, just declare a function that receive input event normally.
const handleChange = e => {
setResumeStates({[e.target.name]: e.target.value})
}
<input
type="text"
name="firstName"
value={values.firstName}
onChange={handleChange}
/>
or if you are feeling func-ky and wanna do func-ception. You can bind onChange with handleChange('firstName').
Note that "handleChange('firstName')" will return a function that accept "e" as parameter.
const handleChange = input => e => {
setResumeStates({[input]: e.target.value})
}
<input
type="text"
value={values.firstName}
onChange={handleChange('firstName')}
/>

How to reset state variables to its initial state after component re-rendering

I am writing a full stack web application to register a user. I have state variables to reflect the back-end errors, which are then used in the jsx code. I re-render the component whenever the alerts change (which are app-level state managed by redux whose content are generated by the backend). The problem I am facing is that, when the first time I don't enter the required info and hit submit, I display the errors successfully below the corresponding wrongly entered field for 1 second before dispatching a clear alert action, and although alerts state are updated according to redux devtool, the error message would still be there after 1 s in the component. I think the problem is that I need to reset the local state variable that corresponds to the field that was cleared, but I am not sure how to implement that. The errors are captured in errorsData state variable below. Here is my component
import React, { useState, useEffect } from 'react';
import { setAlert } from '../../actions/alert';
import { connect } from 'react-redux';
import { registerUser } from '../../actions/auth';
const Register = ({ setAlert, alerts, registerUser }) => {
const [formData, setFormData] = useState({
name: '',
email: '',
password: '',
password2: '',
});
const [errorsData, setErrorsData] = useState({
nameErr: '',
emailErr: '',
passwordErr: '',
});
const { name, email, password, password2 } = formData;
const { nameErr, emailErr, passwordErr } = errorsData;
const handleOnChange = (e) => {
setFormData({ ...formData, [e.target.name]: e.target.value });
};
const handleOnSubmit = async (e) => {
e.preventDefault();
if (password !== password2) {
console.log('Passwords do not match');
} else {
registerUser({ name, email, password });
}
};
useEffect(() => {
alerts.forEach((alert) => {
if (alert.errField === 'name') {
setErrorsData({ ...errorsData, nameErr: alert.msg });
}
if (alert.errField === 'email') {
setErrorsData({ ...errorsData, emailErr: alert.msg });
}
if (alert.errField === 'password') {
setErrorsData({ ...errorsData, passwordErr: alert.msg });
}
});
}, [alerts]);
return (
<form className='form' onSubmit={handleOnSubmit}>
<div className='input-field'>
<label htmlFor='name'>Name</label>
<input
type='text'
name='name'
value={name}
id='name'
placeholder='Enter your name'
onChange={handleOnChange}
/>
<small className='error error--name'>{nameErr}</small>
</div>
<div className='input-field'>
<label htmlFor='email'>Email</label>
<input
type='email'
name='email'
value={email}
id='email'
placeholder='Enter a valid email'
onChange={handleOnChange}
/>
<small className='error error--email'>{emailErr}</small>
</div>
<div className='input-field'>
<label htmlFor='password'>Passwrod</label>
<input
type='password'
name='password'
value={password}
id='password'
placeholder='Enter password'
onChange={handleOnChange}
/>
<small className='error error--password'>{passwordErr}</small>
</div>
<div className='input-field'>
<label htmlFor='password2'>Confirm password</label>
<input
type='password'
name='password2'
value={password2}
id='password2'
placeholder='Confirm password'
onChange={handleOnChange}
/>
</div>
<input className='submit' type='submit' value='Submit' />
</form>
);
};
const mapStateToProps = (state) => ({
alerts: state.alert,
});
export default connect(mapStateToProps, { setAlert, registerUser })(Register);
Your component is setup incorrectly because you are trying to use connect with functional components when you must be using useDispatch and useTypedSelector for redux with functional components. Instead you should do something like this.
import React, { useState, useEffect } from "react";
/* Hooks */
**import { useDispatch, useSelector } from "react-redux";**
/* Actions */
import { registerUser } from "../../actions/auth";
import { setAlert } from "../../actions/alert";
const Register = () => {
// Hooks
**const dispatch = useDispatch();**
// Store
**const alerts = useSelector(state => state.alert);**
// Local state
const [formData, setFormData] = useState({
name: "",
email: "",
password: "",
password2: "",
});
const [errorsData, setErrorsData] = useState({
nameErr: "",
emailErr: "",
passwordErr: "",
});
const { name, email, password, password2 } = formData;
const { nameErr, emailErr, passwordErr } = errorsData;
// Event handlers
const handleOnChange = e => setFormData({ ...formData, [e.target.name]: e.target.value });
const handleOnSubmit = async e => {
e.preventDefault();
if (password !== password2) console.log("Passwords do not match");
else dispatch(registerUser({ name, email, password }));
};
// Effects
useEffect(() => {
alerts.forEach(alert => {
switch(alert.errField) {
case "name":
setErrorsData({ ...errorsData, nameErr: alert.msg });
break;
case "email":
setErrorsData({ ...errorsData, emailErr: alert.msg });
break;
case "password":
setErrorsData({ ...errorsData, passwordErr: alert.msg });
break;
default:
break;
};
});
}, [alerts, errorsData]);
// Rendering
return (
<form className="form" onSubmit={handleOnSubmit}>
<div className="input-field">
<label htmlFor="name">Name</label>
<input
type="text"
name="name"
value={name}
id="name"
placeholder="Enter your name"
onChange={handleOnChange}
/>
<small className="error error--name">{nameErr}</small>
</div>
<div className="input-field">
<label htmlFor="email">Email</label>
<input
type="email"
name="email"
value={email}
id="email"
placeholder="Enter a valid email"
onChange={handleOnChange}
/>
<small className="error error--email">{emailErr}</small>
</div>
<div className="input-field">
<label htmlFor="password">Passwrod</label>
<input
type="password"
name="password"
value={password}
id="password"
placeholder="Enter password"
onChange={handleOnChange}
/>
<small className="error error--password">{passwordErr}</small>
</div>
<div className="input-field">
<label htmlFor="password2">Confirm password</label>
<input
type="password"
name="password2"
value={password2}
id="password2"
placeholder="Confirm password"
onChange={handleOnChange}
/>
</div>
<input className="submit" type="submit" value="Submit" />
</form>
);
};
**export default Register;**
Major changes are highlighted.

Using Hook to control form fields with an Object

I want to create a form to edit a use profile. The form renders data from the user object. I want to use React's useState Hook to hold the state of the form and I want to keep a single object to track changes to the form using an onChange function that handles the changes to the whole user object. Why is this not working?
function Profile() {
const [user, setUser] = useState({});
const [errors, setErrors] = useState({});
useEffect(() => {
axios.get(`/api/v1/users/me`)
.then(res => setUser(res.data.user))
}, [])
const onChange = e => {
user[e.target.name] = e.target.value;
setUser(user)
}
return (
< div >
<form onSubmit={null}>
<div className="form-group">
<label htmlFor={user.name}>Name</label>
<input type="text" name="name"
className={`form-control form-control-lg ${errors.name ? 'is-invalid' : ''}`}
onChange={onChange} placeholder="Fred Flintstone" value={user.name || ''}
/>
</div>
<div className="form-group">
<label htmlFor="email">Email</label>
<input type="email" name="email"
className={`form-control form-control-lg ${errors.email ? 'is-invalid' : ''}`}
onChange={onChange} placeholder="fred.flintstone#aol.com" value={user.email || ''}
/>
</div>
<div className="form-group">
<label htmlFor="username">Username</label>
<input type="text" name="username"
className={`form-control form-control-lg ${errors.username ? 'is-invalid' : ''}`}
onChange={onChange} placeholder="yabadabadu" value={user.username || ''}
/>
</div>
</form>
<div>
<button type="button" className="btn btn-light btn-sm float-right" onClick={() => console.log("Logout")}>Logout</button>
</div>
</div >
)
}
You're modifying the user object in-place. When you call setUser(user), the form won't re-render because the identity of the user object hasn't changed.
Where you have:
const onChange = e => {
user[e.target.name] = e.target.value;
setUser(user)
}
what you want to have instead is something like:
const onChange = useCallback((event) => {
const {name, value} = event.target;
setUser(oldUser => {
return {
...user,
[name]: value,
};
});
}, [setUser]);
As a general rule of thumb, you usually don't want to modify state objects in-place in React.
You should make a copy of users or it will not trigger a render phase as React performs a shallow comparison with the previous state.
const onChange = ({ target: { name, value } }) => {
setUser(user => ({ ...user, [name]: value }));
};
setState() will always lead to a re-render unless shouldComponentUpdate() returns false. If mutable objects are being used and conditional rendering logic cannot be implemented in shouldComponentUpdate(), calling setState() only when the new state differs from the previous state will avoid unnecessary re-renders.
setState API

Categories