React DOM not re-rendering accordingly to setState - javascript

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,
}
});
}

Related

How to Change pre filled input values

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.

React Function Component Form not setting state variables?

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,
}));
};

using ...prev in setState prohibits userEvent.type to type into input when react testing

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 }));
}

how to add onchange event in react js application

I am new to reactjs I want to add onchange in my application. I am using map function for data.
onChange = (event,k,i) => {
this.setState({
dList: update(this.state.dList[k][i], {
[event.target.name]:
{$set: event.target.value}
})
})
}
state example
state = {
name:'',
password:''
}
Incase you have multiple inputs of example name and password
<input type="text" name="name" value={name} onChange={this.handleChange} />
<input type="password" name="password" value={password} onChange={this.handleChange} />
this how you apply the onchange handler
handleChange = e => {
this.setState({[e.target.name]: e.target.value})
}
You should put an onChange function in a onChange html tag attribute.
Example:
<input type="text" value={this.state.value} onChange={this.handleChange} />
Reference
If you want to update your component's state which depends on the previous state of the component, use the overloaded version of setState which takes a function as a parameter. This is because the calls made to the setState function are batched to improve performance.
onChange = (event,k,i) => {
this.setState(prevState => {
dList: update(prevState.dList[k][i], {
[event.target.name]:
{$set: event.target.value}
})
})
}

React - object assign multiple dynamic input values

I am trying to set state of dynamically generated inputs. The initial tasks object where I want to set the new state looks like so:
The render method:
render(){
return(
<div>
<main className="content">
<form onSubmit={this.onSubmit}>
<div>
{Object.keys(this.state.dataGoal).map( (key, index) => {
return <div key={key}>
<label>{this.state.dataGoal[key]}</label>
<div className="input-wrap">
<input
type="text"
name={`${key}-task-${index}`}
value={this.state.tasks[key]}
onChange={this.handleInputChange} />
</div>
</div>;
})}
</div>
<div className="input-wrap">
<input
className="primary-btn"
type="submit"
value="Set my goal!"
onClick={this.formReset} />
</div>
</form>
</main>
</div>
);
}
and finally the handleInputChanged function:
handleInputChange = (e) => {
const value = e.target.value;
const name = e.target.name;
this.setState({
tasks: Object.assign({}, this.state.tasks, {[name]: value})
});
}
I want to set the new state of object when one of the inputs is changed. The desired result is to get the input value and set it to name key as an value in tasks object.
I also want to ask if the input names must be unique.
Thanks for any help,
Jakub
This looks like you're on the right path, the only thing missing is we will have to tell handleInputChange what index we want to update in tasks. We will have to use Object.assign twice because it's a nested structure. In this case if we assume the indices of dataGoal match up with tasks, we can pass in the index provided by the map function.
In our render function:
<input
type="text"
name={`${key}-task-${index}`}
value={this.state.tasks[key]}
onChange={(e) => this.handleInputChange(e, index)} />
// Notice: This will cause a performance hit
// We are binding 'this' using fat arrow every render but it shows the idea
Handling the input:
handleInputChange = (e, index) => {
const value = e.target.value;
const name = e.target.name;
const tasks = this.state.tasks;
const updatedTask = Object.assign({}, tasks[index], { [name]: value });
this.setState({
tasks: Object.assign({}, tasks, { [index]: updatedTask })
});
}
Input names don't have to be unique in this case, as the index will provide 'uniqueness'.
Using destructing instead of Object.assign:
handleInputChange = (e, index) => {
const value = e.target.value;
const name = e.target.name;
this.setState({
tasks: {
...this.state.tasks,
[index]: {
...this.state.tasks[index],
[name]: value
}
}
});
}

Categories

Resources