my onChange event is not working on input elements - javascript

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')}
/>

Related

'A component is changing a controlled input to be uncontrolled' and partial state update

I have a parent component that is interested in gathering a list of contacts (name+email):
import { useState } from 'react'
import AddContactFn from './components/AddContactFn'
function App() {
const [contacts, setContacts] = useState([])
const addNewContact = (newContact) => {
setContacts([...contacts, newContact])
}
return (
<div>
<AddContactFn newContact={addNewContact} />
</div>
)
}
export default App
through a child component, which renders a form, with two inputs fields and an 'Add' button:
import React from 'react'
import { useState } from 'react'
export default function AddContactFn({ newContact }) {
const [contact, setContact] = useState({name: ' ', email: ' '})
const addContact = (e) => {
e.preventDefault()
if (contact.name === ' ' || contact.email === ' ') {
return
}
newContact(contact)
}
return (
<div>
<h2>Add Contact</h2>
<form onSubmit={addContact}>
<label>Name</label>
<input
type='text'
name='name'
value={contact.name}
placeholder='Name'
onChange={e => setContact({ name: e.target.value })}
/>
<label>Email</label>
<input
type='text'
name='email'
value={contact.email}
placeholder='Email'
onChange={e => setContact({ email: e.target.value })}
/>
<button type='submit'>Add</button>
</form>
</div>
)
}
I am facing two issues:
Warning: A component is changing a controlled input to be uncontrolled. This is likely caused by the value changing from a defined to undefined, which should not happen. Decide between using a controlled or uncontrolled input element for the lifetime of the component.
with both input fields containing text, every time the Add button is pressed, only the last edited value is returned to the parent (ie. either 'name' or 'email' is returned, not both)
Lets fix one at a time
The warning:
Try adding a check for the input value like value={contact.name || ''}
The OR || operator will check if contact.name is valid, if so use it otherwise ''
This is a side effect linked to the second issue
The state:
Your onChange handler needs to add the previous object properties, currently they are not, so try onChange={e => setContact(prevState, ({...prevState, name: e.target.value }))}
By doing onChange={e => setContact({ name: e.target.value })} you are losing all the properties of the state object, and setting the state to only have a name or email property, that is why you get the warning since the name or email are lost.
Since you have two input fields (name & email) you will have to do the same on both:
<label>Name</label>
<input
type='text'
name='name'
value={contact.name || ''}
placeholder='Name'
onChange={e => setContact(prevState => ({...prevState, name: e.target.value}))}
/>
<label>Email</label>
<input
type='text'
name='email'
value={contact.email || ''}
placeholder='Email'
onChange={e => setContact(prevState => ({...prevState, email: e.target.value}))}
/>
When you use setContact({ name: e.target.value }) (or for the email) you are also setting the other field as undefined. The way I would recommend you did this is:
import React from 'react'
import { useState } from 'react'
export default function AddContactFn({ newContact }) {
const [name, setName] = useState(' ');
const [email, setEmail] = useState(' ');
const addContact = (e) => {
e.preventDefault()
if (name === ' ' || email === ' ') {
return
}
newContact({ name, email })
}
return (
<div>
<h2>Add Contact</h2>
<form onSubmit={addContact}>
<label>Name</label>
<input
type='text'
name='name'
value={name}
placeholder='Name'
onChange={e => setName(e.target.value)}
/>
<label>Email</label>
<input
type='text'
name='email'
value={email}
placeholder='Email'
onChange={e => setEmail(e.target.value)}
/>
<button type='submit'>Add</button>
</form>
</div>
)
}
If you want to keep the state as it currently is then you can just do e.g. setContact({ ...contact, name: e.target.value })
You are using Controlled Input where you bind the input tag value to one of contact state property and the onChange event to an arrow function that will update the contact state.
Initially, the contact state has two properties, which are name, and email.
const [contact, setContact] = useState({name: ' ', email: ' '})
...
return (
<input
value={contact.name}
onChange={e => setContact({ name: e.target.value })}
/>
<input
value={contact.email}
onChange={e => setContact({ email: e.target.value })}
/>
)
When the onChange event occurs, it will update the contact state with a new object that only has one property. Here, React will raise a warning as one of the input values detects the change and found that the binding reference is missing/undefined. At that moment, your input has become an Uncontrolled Input. That's why you got this error:
Warning: A component is changing a controlled input to be
uncontrolled. This is likely caused by the value changing from a
defined to undefined, which should not happen. Decide between using a
controlled or uncontrolled input element for the lifetime of the
component.
To fix it, you can clone the contact by using a shallow copy and update your setter like this:
onChange={e => setContact({ ...contact, name: e.target.value })}
onChange={e => setContact({ ...contact, email: e.target.value })}
So, whenever onChange event occurs, the updated contact state will always have two properties both name, and email. Of course, you can use the other technique, as long as the updated value always has consistent keys and the binding reference remain.
Here is an example:

React Input don't change when I set a initial value

I am creating CRUD using ReactJS but I have a problem with the React Forms.
in some cases, I need to set an initial value in my input and I am using a logical operator for this, everything is ok, but when I change the value from the input, the value from the input doesn't change.
My component:
export function Modal(props) {
const [inputsValues, setInputsValues] = useState({
id: '',
nameInput: '',
emailInput: '',
phoneInput: ''
})
const handleInputChange = (event) => {
console.log(event.target.value)
inputsValues[event.target.name] = event.target.value
setInputsValues()
}
const handleFormSubmit = (event) => {
event.preventDefault()
console.log(inputsValues)
}
return (
<div className={`area-modal ${props.isOpen ? 'open' : null}`}>
<div className="modal">
<h1>{props.title}</h1>
<form onSubmit={handleFormSubmit} action="">
<label htmlFor="">Name</label>
<div className="area-input">
<input
type="text"
name="nameInput"
value={inputsValues.nameInput}
onChange={handleInputChange}
/>
</div>
<label htmlFor="">Phone</label>
<div className="area-input">
<input
type="text"
name="phoneInput"
value={props.dataForm ? props.dataForm.phone : ''}
onChange={handleInputChange}
/>
</div>
<label htmlFor="">Email</label>
<div className="area-input">
<input
type="email"
name="emailInput"
value={props.dataForm ? props.dataForm.email : ''}
onChange={handleInputChange}
/>
</div>
<button>{props.buttonText}</button>
</form>
</div>
</div>
)
}
Instead of
inputsValues[event.target.name] = event.target.value;
setInputsValues();
you need to use setInputsValues to update the state with the new value:
const { name, value} = e.target;
setInputsValues({ ...inputsValues, [name]: value });
You need to pass the updated input data into your setInputsValues, for example:
const handleInputChange = (event) => {
console.log(event.target.value)
inputsValues[event.target.name] = event.target.value
setInputsValues({...inputsValues});
}
When working with object-based states like this, it is usually good practice to use the destructured assignment to ensure the state updates.
When you're using reacts useState, you don't need to set the state manually. It handles state for you. Also it's better to use object spread to change the state. So you can change handleInputChange same the bellow:
const handleInputChange = (event) => {
const name = event.target.name;
const value = event.target.value;
setInputsValues({...inputsValues, [name] : value })
}

React JS (Form Update Not Working) - How to set state immediately after getting data from API using mapDispatchToProps?

I am very new to react and working on some basics where I came up in the situation - I want to set state immediately after API call.
Scenario:
2 Forms:
1st form => accepts id and calls api to get data of single user
2nd form => updates data
PROBLEM: I want to set state when I get data after clicking submit button on 1st Form
import React, { Component, useEffect } from 'react'
import { connect } from 'react-redux';
import { getSingleUser } from '../redux/user/userActions';
export class UsersContainerUpdate extends Component {
constructor(props) {
super(props);
console.log(props.propFirstName);
this.state = {
id: '',
// first_name: props.propFirstName === '' ? '' : props.propFirstName,
first_name: props.propFirstName,
last_name: props.propLastName === '' ? '' : props.propLastName,
phone: props.propPhone === '' ? '' : props.propPhone,
email: props.propEmail === '' ? '' : props.propEmail,
address: props.propAddress === '' ? '' : props.propAddress,
city: props.propCity === '' ? '' : props.propCity,
state: props.propState === '' ? '' : props.propState,
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.handleUpdate = this.handleUpdate.bind(this);
}
handleChange = (field, event) => {
this.setState({ [field]: event.target.value });
}
handleSubmit(event) {
// alert('A name was submitted: ' + this.state.name);
event.preventDefault();
const {
id
} = this.state;
const postData = {
id: id
};
// console.log(this.state);
// console.log(postData);
this.props.getSingleUserData(id);
// if (this.props.getSingleUserData(id)) {
// this.setState({
// ...this.state,
// first_name: this.props.propFirstName
// });
// }
}
handleUpdate(event) {
// alert('A name was submitted: ' + this.state.name);
event.preventDefault();
const {
first_name,
last_name,
phone,
email,
address,
city,
state
} = this.state;
const postData = {
first_name: first_name,
last_name: last_name,
phone: phone,
email: email,
address: address,
city: city,
state: state
};
console.log(this.state);
console.log("POSTDATA:", postData);
// alert('hi');
// this.props.updateUserData(id,postData);
}
render() {
return (
<div>
<h1>Update User By ID</h1>
<form onSubmit={this.handleSubmit}>
<div>
<label>ID:</label>
<input
type="text"
value={this.state.id}
onChange={(event, newValue) => this.handleChange('id', event)}
/>
</div>
<div>
<input type="submit" value="Submit" />
</div>
</form>
<div>
<h1>Update User</h1>
<form onSubmit={this.handleUpdate}>
<div>
<label>First Name:</label>
<input
type="text"
value={this.state.first_name || this.props.propFirstName}
onChange={(event, newValue) => this.handleChange('first_name', event)}
/>
</div>
<div>
<label>Last Name:</label>
<input
type="text"
value={this.state.last_name || this.props.propLastName}
onChange={(event, newValue) => this.handleChange('last_name', event)} />
</div>
<div>
<label>Phone:</label>
<input
type="text"
value={this.state.phone || this.props.propPhone}
onChange={(event, newValue) => this.handleChange('phone', event)} />
</div>
<div>
<label>Email:</label>
<input
type="text"
value={this.state.email || this.props.propEmail}
onChange={(event, newValue) => this.handleChange('email', event)} />
</div>
<div>
<input type="submit" value="Submit" />
</div>
</form>
<div>
Notice Message : {this.props.propFirstName}
</div>
</div>
</div>
)
}
}
const mapStateToProps = state => {
console.log(state.user);
return {
propFirstName: state.user.first_name,
propLastName: state.user.last_name,
propPhone: state.user.phone,
propEmail: state.user.email,
propAddress: state.user.address,
propCity: state.user.city,
propState: state.user.state
}
}
const mapDispatchToProps = dispatch => {
return {
getSingleUserData: id => dispatch(getSingleUser(id)),
// updateUserData: (id,postData) => dispatch(updateUser(id,postData))
}
}
export default connect(mapStateToProps, mapDispatchToProps)(UsersContainerUpdate)
The console outputs are
The console output of line:81 is the current state which is currently empty. I want to set it there.
Thanks in advance. Cheers!!
If your requirement is just to state after API call inside this.props.getSingleUserData(id),
Approach 1: (Unclean)
Add one more argument to getSingleUserData(id, setState) and pass it this.setState as an argument and inside getSingleUserData you can set the state using the function reference passed
Approach 2:
You can return a promise from getSingleUserData and do setState once it is resolves
Suggestion:
Divide your big component into individual components (like one for getting user ID and one for User data updation). The more we identify and split our project into meanigfull individual components we get more clean codes. Also when you choose to move towards functional components you can reduce lot of boiler plates with hooks.
Problem
state.user is used to set the initial value of your component's state. Changes to those props do not change your state after the component is created. They do change the values in your inputs because the initial value was an empty string '' so you default to showing the value from props. This is very misleading since those inputs don't reflect the current state.
I bet I could delete at least half of this code but that's besides the point. But take a moment to think about why props.propState === '' ? '' : props.propState is always exactly the same as just props.propState.
Solution
I have two key recommendations for how I would rewrite this:
Select user by id
Separate into multiple components
Store only the modifications in the state
Create a selector function selectUserById that selects a user from your Redux state by the id. I don't think it makes sense to store the current user properties as top-level properties of state.user like you have them right now. It seems like you also have a property state.user.users which is an array of all loaded users so I would use that.
const selectUserById = (state, id) => state.user.users.find(user => user.id === id);
Or better yet, store an object of users keyed by id.
const selectUserById = (state, id) => state.user.users[id];
With this approach we either have a complete user object or we have undefined. It's easy to check for undefined and not show the "Update User" form at all until we have real data. This makes more sense than using empty strings as the default.
We can access the complete user object from Redux. I would not duplicate that object in state. Instead I would use the state only for the properties that you have changed. You would start out with the state as an empty object and add properties to it as you modify their inputs. You can always combine the two together using object spreading.
const merged = {...existing, ...changes}
Can you implement these suggestions using class components and connect? Yes. But why add the extra hurdle? Some of the things in your code like this.handleChange.bind(this) are relics of the past when we had to do that because there wasn't a better way. But now we have better ways so you should use them.
Code
Interactive Demo on CodeSandbox
import "./App.css";
import React, { useEffect, useState } from "react";
import { useSelector, useDispatch } from "../store";
import { getSingleUser, updateUser } from "../store/slice";
const selectUserById = (state, id) => state.user.users[id];
const UserIdForm = ({ submitId }) => {
const [id, setId] = useState("");
const handleSubmit = (event) => {
event.preventDefault();
submitId(id);
};
return (
<form onSubmit={handleSubmit}>
<div>
<label>ID:</label>
<input
type="text"
value={id}
onChange={(event) => setId(event.target.value)}
/>
</div>
<div>
<input type="submit" value="Submit" />
</div>
</form>
);
};
const UpdateUserForm = ({ id }) => {
const [changes, setChanges] = useState > {};
const existing = useSelector((state) => selectUserById(state, id));
const dispatch = useDispatch();
// respond to changes in id by clearing the changes state and requesting the user
useEffect(() => {
dispatch(getSingleUser(id));
setChanges({});
}, [dispatch, setChanges, id]);
if (!existing) {
return <div>Loading User...</div>;
}
const merged = { ...existing, ...changes };
const handleChange = (property, event) => {
// in function components you have to copy the whole state
setChanges((prevChanges) => ({
...prevChanges,
[property]: event.target.value
}));
};
const handleUpdate = (event) => {
event.preventDefault();
const postData = { ...merged, id };
console.log("POSTDATA:", postData);
dispatch(updateUser(postData));
};
const renderInput = (property, label) => {
return (
<div>
<label>
{label}
<input
type="text"
value={merged[property]} // shows the current value or the updated value
onChange={(event) => handleChange(property, event)}
/>
</label>
</div>
);
};
return (
<form onSubmit={handleUpdate}>
{renderInput("first_name", "First Name:")}
{renderInput("last_name", "Last Name:")}
{renderInput("phone", "Phone:")}
{renderInput("email", "Email:")}
<div>
<input type="submit" value="Submit" />
</div>
</form>
);
};
const UsersContainerUpdate = () => {
// this is the id that was last submitted.
const [id, setId] = useState();
return (
<div>
<div>
<h1>Update User By ID</h1>
<UserIdForm submitId={setId} />
</div>
{!!id && ( // only load when there is an actual id
<div>
<h1>Update User</h1>
<UpdateUserForm id={id} />
</div>
)}
</div>
);
};
export default UsersContainerUpdate;

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

Get specific state value when using custom Hooks ( trying to implement a form handling with hooks)

I'm implementing a form using React hooks instead of classes and states. This is my form:
const { values, handleChange, handleSubmit } = useForm();
const [gender, setGender] = useState("");
return (
<>
<ul className="collapsible">
<li>
<div className="collapsible-header">Step 1: Your details</div>
<div className="collapsible-body">
<div>
<label>First name</label>
<input className="browser-default" type="text" name="name" value={values.name} onChange={handleChange}/>
</div>
<div><label>Last name</label>
<input className="browser-default" type="text" name="lastName" value={values.lastName} onChange={handleChange}/> </div>
<div><label>Email</label>
<input className="browser-default" type="text" name="email" value={values.email} onChange={handleChange}/>
</div>
<button type="submit" className="browser-default">Next></button>
</div>
this is my custom hook component useForm:
import { useState } from 'react';
const useForm = (callback) => {
const [values, setValues] = useState({});
const handleSubmit = (event) => {
if (event) event.preventDefault();
callback();
};
const handleChange = (event) => {
event.persist();
setValues(values => ({ ...values, [event.target.name]: event.target.value }));
};
return {
handleChange,
handleSubmit,
values,
}
};
export default useForm;
now I would like to create an Object with the data of the submitted form to post with Axios on a server. The object should be like this:
let data ={
firstName: name,
lastName: lastName,
email:email,
number:number,
gender:gender,
dob: `${day}-${month}-${year}`,
comments: comments
}
considered that with the custom Hook I've created basically only one state "values", how can i assign the respective value to each property? For example " firstName: [event.target.name] " would work?
Thank you guys!
Can you not just do this?
let data ={
firstName: values.name,
lastName: values.lastName,
email: values.email,
... etc
}
Values is an object, so to use its individual values you would just access the corresponding keys. values.your_key.
Your example will work fine, you have to take care of below issues:
firstName: you don't have a form field with that name. because you are depending on mapping between form fields names and JSON object, so you have to make sure that each form field name is exactly what you want in your JSON object key.
You will get A component is changing an uncontrolled input of type text to be controlled. error, because the form field value will have undefined as initial value. two possible fixes, the easiest is on each form field the value attribute to be like value={values.name || ''}.

Categories