This question already has an answer here:
How to build a nested object on handleChange with React?
(1 answer)
Closed 13 days ago.
const [info, setInfo] = useState({
idx: 1,
user: {
name: ""
}
});
function handle(e) {
setInfo(prev => ({ ...prev, [e.target.name]: e.target.value }));
}
<input type="text" onChange={(e) => handle(e)} name="idx" /> // works!
<input type="text" onChange={(e) => handle(e)} name="user.name" /> // NOT work
how can i change user.name value of useState as name attribute of text input?
try:
name="user.name"
name="user['name']"
Because name is nested in user it would be easier to have an independent onChange function, as your current handle function will only set top-level keys using e.target.name
<input type="text" onChange={(e) => setInfo(prevState => {...prevState, user: {name: e.target.value}})}/>
Related
I new to React. I have two input fields which are filled when the pages loads via axios but when I want to change the Values I am unable to change them even I can not type anything on them.
function MainView() {
const [InputFields, setInputFields] = useState({
name: "",
fullURL: "",
});
const changeHandler = (e) => {
setInputFields({
...InputFields,
[e.target.name]: e.target.value,
});
};
const [links, setLinks] = useState([]);
const getLinks = () => {
axios.get('../sanctum/csrf-cookie').then(response => {
axios.get("/userlinks/getdata").then((res) => {
console.log(res);
setLinks(res.data);
}).catch((err) => {
console.log(err);
});
});
};
useEffect(() => {
getLinks();
}, []);
return (<div>
{links.map((link) => {
return (<div className="card" key={link.id}>
<form>
<input className="form-control" value={link.name} type="text" name="name" placeholder="name" onChange={changeHandler} />
<input className="form-control" value={link.fullURL} type="text" name="fullURL" placeholder="fullURL" onChange={changeHandler} />
</form>
</div>);
})}
</div>
);
}
export default MainView;
This happens because in your onChange method, you are changing InputFields variable, where as your getLinks method changes links variable, which is being rendered on the screen.
If you want to set an initial value, and then allow the user to change it, change your input to :
<input className="form-control" defaultValue={link.name}
value={InputFields.name} type="text" name="name" placeholder="name" onChange={changeHandler} />
Likewise change for your other input, if you do not want the user to change the value later on, it's often better to add disable in the input to avoid confusing people. 🙂
I know that this has been done so that you can create a minimal reproducible example for us, but I would have directly called setInputFields in the axios.get section to avoid this problem in the first place, however, if not possible, use the defaultValue and value as I've shown above.
I have a React Function Component that is a form where users enter in values. The user inputs are stored using Hooks. The issue that I am running into is that it does not seem to be setting the hook correctly. When I console log one value right before the axios call, it returns as undefined. Below is the code I have so far. I think I am pretty close but am unsure where I made mistakes. Any help is appreciated!
import React, { useState, useEffect } from "react";
import axios from "axios";
function ReportOutage(){
//Use state hook to hold the values users input, passed into axios call
//setting the default values
const [formData, setFormData] = useState({
userReport: '6',
serviceType: "",
serviceName: "",
serviceStreet: "",
serviceCity: "",
serviceState: "",
serviceDescription: ""
})
const handleChange = (event) => {
event.preventDefault();
setFormData({[event.target.name]: event.target.value})
};
const handleSubmitReport = async (event) =>{
event.preventDefault();
console.log(formData.serviceName); // This is returning undefined when I am expecting the service name entered by the user.
const res = await axios.post("/outage-new", {
user_id: `${formData.userReport}`,
service_type: `${formData.serviceType}`,
service_name: `${formData.serviceName}`,
outage_street: `${formData.serviceStreet}`,
outage_city: `${formData.serviceCity}`,
outage_state: `${formData.serviceState}`,
outage_description: `${formData.serviceDescription}`,
})
};
return (
<>
<h1 id="Report-Title" class>Test Dialog box</h1>
<form onSubmit={handleSubmitReport}>
<input type="text" placeholder="Service Type"
onChange={handleChange}
value={formData.serviceType}
name="serviceType"/>
<input type="text" placeholder="Service Name"
onChange={handleChange}
value={formData.serviceName}
name="serviceName"/>
<input type="text" placeholder="Street"
onChange={handleChange}
value={formData.serviceStreet}
name="serviceStreet"/>
<input type="text" placeholder="City"
onChange={handleChange}
value={formData.serviceCity}
name="serviceCity"/>
<input type="text" placeholder="State"
onChange={handleChange}
value={formData.serviceState}
name="serviceState"/>
<input type="text" placeholder="Description"
onChange={handleChange}
value={formData.serviceDescription}
name="serviceDescription"/>
<button type="submit">Report Outage</button>
</form>
</>
);
}
export default ReportOutage;
When using the useState hook, updates are not shallowly merged like they are in the class component's this.setState. You are replacing the state with a new object with only the last field value updated.
const handleChange = (event) => {
event.preventDefault();
setFormData({ // <-- new object with only field name/value
[event.target.name]: event.target.value
});
};
You must manage this yourself, manually. Use a functional state update to access and shallow copy the previous state into the next state object.
const handleChange = (event) => {
event.preventDefault();
const { name, value } = event.target;
setFormData(data => ({
...data,
[name]: value,
}));
};
I am attempting to send form data from my react client to my nodejs server; however, upon submitting the form, only the last property of the state is sent to my server. I know this is a client side issue, since with Postman, my entire form data is sent to my database.
It seems when I add a value to each property in my state, only that property is maintained in user. I haven't been able to resolve this in the time I allotted myself, so I would appreciate another perspective / feedback on the issue.
const ModalForm = ({ show }) => {
if (!show) {
return null;
}
let [user, setUser] = useState({
firstName: '',
lastName: '',
email: '',
password: '',
age: ''
});
let handleChange = (e) => {
console.log('event name', e.target.name, 'event value', e.target.value);
setUser({
[e.target.name]: e.target.value
});
};
/*
currently, only the last prop and value are sending to my API
need to resolve so that all form data is sent to API
*/
let handleSubmit = (e) => {
e.preventDefault()
let data = user;
setUser({[e.target.name]: e.target.value});
axios.post('/new/user', data)
.then(res => console.log(res))
.catch(err => console.error(err))
}
console.log('user:', user);
return (
<div className='form-container'>
<form className='form-space' onSubmit={handleSubmit}>
<label>
First Name:<br/>
<input type='text' name='firstName' onChange={handleChange}/>
</label><br/>
<label>
Last Name:<br/>
<input type='text' name='lastName' onChange={handleChange}/>
</label><br/>
<label>
Email:<br/>
<input type='email' name='email' onChange={handleChange}/>
</label><br/>
<label>
Password:<br/>
<input type='password' name='password' onChange={handleChange}/>
</label><br/>
<label>
Age:<br/>
<input type='text' name='age' onChange={handleChange}/>
</label><br/>
<input type='submit' value='Submit' />
</form>
</div>
)
};
export default ModalForm;
useState state setters are not like setState in functional components; in functional components, properties of the state object are kept even when setState is called with an object lacking those properties. It's like Object.assign.
In class components, starting with a state of { foo: true } and doing setState({ bar: true }) results in { foo: true, bar: true }.
But useState in functional components is not like that. Instead of merging the old state with the new state, the new state completely replaces the old state. Starting with a state of { foo: true } and doing setState({ bar: true }) results in { bar: true }.
Here, since you're doing
setUser({
[e.target.name]: e.target.value
});
any previous properties of user get lost.
Spread the previous value of user into the new state instead, so the previous state properties get preserved:
setUser({
...user,
[e.target.name]: e.target.value
});
I want to test my Log In Component, which consists of two input fields, whose values are determined by a single react state that is an object with two parameters. However, when I try the test only the first letter appears in the value of the selected input and not the rest of the word. I determined my use of ...prev when updating the state to be the issue. If I only use a single input field with one state it works fine!
Here is my component:
import {useState} from 'react';
export function Login () {
//Login Credentials
const [loginCredentials, setLoginCredentials] = useState({ name: '' });
const handleChange = ({target}) => {
setLoginCredentials({[target.name]: target.value});
}
return (
<div className="login-container">
<h1>Log In</h1>
<div className="login-label-input">
<label htmlFor="name">Account Name
<input
type="name"
id="name"
name="name"
onChange={handleChange}
value={loginCredentials.name}
/>
</label>
<label htmlFor="name">Password
<input
type="password"
id="password"
name="password"
onChange={handleChange}
value={loginCredentials.password}
/>
</label>
</div>
State name: {loginCredentials.name}. State password: {loginCredentials.password}.
</div>
)
}
This works but if I include the password state:
export function Login () {
//Login Credentials
const [loginCredentials, setLoginCredentials] = useState({ name: '', password: '' });
const handleChange = ({target}) => {
setLoginCredentials((prev) => ({...prev, [target.name]: target.value}));
}
...
it does not pass the test. I does not throw an error but simply only adds the first letter of the string I am testing with:
test("log in with empty name input returns error message", async () => {
render(
<Login />
);
const nameField = screen.getByLabelText(/account name/i);
userEvent.type(nameField, 'test');
await waitFor(() => expect(nameField).toHaveValue('test'));
});
with the error:
expect(element).toHaveValue(test)
Expected the element to have value:
test
Received:
t
Is using ...prev bad or is this is a bug or what is going on?
It seems like you have to assign the new value to a different variable, I am not sure why this is necessary for the test but not in the app itself.
const handleChange = ({target}) => {
const newValue = target.value
setLoginCredentials((prev) => ({ ...prev, [target.name]: newValue }));
}
I am working on a part of a React application where a todo can be created, with the following JSX code:
<form>
<div>
<label htmlFor="subject">Subject: </label>
<input type="text" value={todo.subject} onChange={e => handleInput(e, 'subject')} name="subject" id="subject"></input>
</div>
<div>
<label htmlFor="duedate">Due date: </label>
<input type="date" value={todo.duedate} onChange={e => handleInput(e, 'duedate')} name="duedate" id="duedate"></input>
</div>
<div>
<label htmlFor="description">Description: </label>
<textarea value={todo.description} onChange={e => handleInput(e, 'description')} name="description" id="description"></textarea>
</div>
<div>
<label htmlFor="sidenote">Sidenotes: </label>
<textarea value={todo.sidenote} onChange={e => handleInput(e, 'sidenote')} name="sidenote" id="sidenote"></textarea>
</div>
<button type="submit" onClick={e => handleSubmit(e)}>Submit</button>
</form>
The todo state and handleInput are defined as follows:
const [todo, setTodo] = useState({
'subject': '',
'duedate': '',
'description': '',
'sidenote': ''
});
const handleInput = (e, field) => {
e.persist();
setTodo(prevTodo => {
prevTodo[field] = e.target.value;
return prevTodo;
});
}
By running console.log(todo) after input changes, I can confirm that the state updates properly; however, these state changes are not rendered as the value for the input elements. Conversely, changing handleInput to the following solved the problem:
const handleInput = (e, field) => {
e.persist();
setTodo(prevTodo => {
let newTodo = {...prevTodo};
newTodo[field] = e.target.value;
return newTodo;
});
}
Considering that both functions correctly updated the todo state, what has caused the latter function to work but not the former one?
Generally, in your case you have to return a shallow copy object rather than just modify the key of the current reference state since React won't compare deep in key, it just compares by === to check if current state & next state is different then decide to re-render or not, you can also re-write to be working also:
const handleInput = (e, field) => {
e.persist();
setTodo(prevTodo => {
return {
...prevTodo,
[field]: e.target.value,
}
});
}