Updating a nested array element in React setState - reactjs

I am maintaining an array of objects which is stored in a state object. Basically I am pushing each object to this array whenever I click on Add button .This stores this object in array.
I am maintaining a flag updateButtonFlag to show the update button for that particular account.
I want to update this flag of an account that just got submitted(that is in onAddAccount() function).
After addition , a new card gets displayed with input fields, so that next user details can be entered
Help would be appreciated
//Have included only onAddAccount function ,where the logic needs to go.
//There is a fetch call as well, which basically displays accounts info if there are any accounts w.r.t to that user
import * as React from 'react';
interface IState{
users : Account[];
user: Account
}
interface Account{
name: string;
email: string;
phone: string;
updateButtonFlag: boolean
}
export default class App extends React.Component<{},IState> {
constructor(props:any){
super(props);
this.state= {
users: [],
user: null
}
}
async componentDidMount(){
let useraccounts = await this.fetchAccounts(); // call that returns accounts, if present
let id:any, account: IAccount ;
if(useraccounts.length === 0) // if no account, display an empty card
{
this.setState({ accounts: [...this.state.accounts, {firstname:'',lastname:'',phone:'',updateButtonFlag: false}]},()=>{});
}
if(useraccounts.length > 0) // if there are accounts existing, display themand add update button to them
{
let accountArray = [];
for(let i=0;i<useraccounts.length;i++)
{
account = {
firstsname: useraccounts[i].firstsname,
lastname: useraccounts[i].lastname,
phone: useraccounts[i].phone,
updateButtonFlag: true
}
accountArray.push(account);
}
this.setState(({accounts}) => ({accounts: [...accounts, ...accountArray]}),()=>{});
}
}
onAddAccount = (index:number) => { // this adds one more card with input fields after submission of current user info
let { users } = this.state;
let account : IAccount = {firstname: users[index].firstname, lastname: users[index].lastname , phone: users[index].phone, updateButtonFlag:false} // maintaining a updateflag to show update button for the corresponding account
this.submit(account); // submit call to submit the account details
//here i need to update the flag of currently submitted account to true, so that update button gets shown , how to do it?
this.setState((prevState) => ({
users: [ ...prevState.users, {firstname:'',lastname:'',phone:''updateButtonFlag:false} ],
}));
} // in this line,next card gets added here
}
renderAccounts = (users: Account[]) => {
return accounts.map((value, index) => {
return (
<div key={index}>
<div>
<form>
<label>First Name:</label>
<input
type="text"
name="firstname"
value={value.firstname}
onChange={e => this.handleChange(e, index)}
required
/>
<label>Last Name:</label>
<input
type="text"
name="lastname"
value={value.lastname}
onChange={e => this.handleChange(e, index)}
/>
<label>Age:</label>
<input
type="text"
name="age"
value={value.age}
onChange={e => this.handleChange(e, index)}
required
/>
<div>
<button onClick={() => this.onAddAccount(index)}>
Save & Add Another Account
</button>
{users[index].updatedButtonFlag?<button onClick={() => this.onUpdateAccount(index)}>
Update Account
</button> :null}
<button onClick={() => this.onRemoveAccount(index)}>
Remove Account
</button>
)}
</div>
</form>
</div>
</div>
);
});
};
render() {
return <div>{this.renderAccounts(accounts)}</div>;
}
}
}

Following what I saw on this thread, you cannot use setState to update nested objects. So, in your case, you'll have to update the entire array.
onAddAccount = (index:number) => {
let { users } = this.state;
let account : IAccount = {firstname: users[index].firstname, lastname: users[index].lastname , phone: users[index].phone, updateButtonFlag:false}
this.submit(account);
users[index].updateButtonFlag = true;
users.push({firstname:'',lastname:'',phone:'',updateButtonFlag:false}); // Add an empty account
this.setState({
users: users,
}));
}

Related

React login form loop isn't re-rendering DOM

I'm trying to make a login component and I think my issue is with React not re-rendering the DOM in my browser but I'm not sure why
If I leave the password field blank when I press the main 'Login' button in my form it will render the alert / warning message .. I can then click this message to dismiss it which is exactly what I want
If I were to repeat the process I would expect the message to be re-rendered and the DOM element reintroduced, however this is not the case - I can see that the loop is being run, I am getting all of the console logs with the correct values, however the loop does not seem to run the 'return' part of my if statement on the second try (in the code below I've added 'this return doesn't re-render' to the console log before that return) - here's my code
Apologies for the large code snippet but I felt it was all relevant for this question
class LoginForm extends Component {
constructor() {
super();
this.state = {
email: "",
password: "",
errors: [],
};
this.onLoginClick = this.onLoginClick.bind(this);
}
onLoginClick() {
const username = this.state.email.trim();
const password = this.state.password.trim();
let errors = [];
console.log("Login press")
if (!EMAIL_REGEX.test(username)) {
errors.push(error_user);
console.log("Username error")
}
if (password === "") {
errors.push(error_pass);
console.log("Password is blank")
}
if (errors.length === 0) {
this.props.onLoginClick(username, password);
if (this.props.loginStatus === login_f) {
errors.push(error_cred);
}
}
this.setState({
errors: errors,
});
console.log("Here are the errors", errors)
}
handleEmailChange = (e) => {
this.setState({ email: e.target.value });
};
handlePasswordChange = (e) => {
this.setState({ password: e.target.value });
};
clearAlertsHandler() {
console.log("Clear alerts")
document.getElementById("misMatch").remove()
}
render() {
let updatedErrors = [...this.state.errors];
return (
<fieldset>
{updatedErrors.map((errorMessage, index) => {
if (errorMessage === error_cred) {
console.log("error_cred match", error_cred, errorMessage)
return (
<button key={index} id={"match"}>{errorMessage} - click to clear</button>
);
} else {
console.log("error_cred mismatch - this return doesn't re-render", error_cred, errorMessage)
return (
<button key={index} id={"misMatch"} onClick={(e) => this.clearAlertsHandler(e)}>{errorMessage} - click to clear</button>
);
}
})}
<label className="text-uppercase">Username</label>
<input
name="email"
type="text"
value={this.state.email}
placeholder="username"
onChange={this.handleEmailChange}
/>
<label className="text-uppercase">Password</label>
<input
className="mb20"
name="password"
type="password"
value={this.state.password}
placeholder="••••••••••"
onChange={this.handlePasswordChange}
/>
<button name="submit" className="primary mb20" onClick={this.onLoginClick}>
Login
</button>
</fieldset>
);
}
In my opinion, React doesn't know that error array changed if you don't clear it.
I think you should do something like this:
clearAlertsHandler() {
console.log("Clear alerts")
this.setState({
errors: [],
});
document.getElementById("misMatch").remove()
}

Objs not valid as children

I have created a project that calls out to the WHOIS API ( which takes Domain, IPv4, IPv6, and email address as valid input ) I have this working properly with the domain address input but when I input an IP address this is the error message I get. Error: Objects are not valid as a React child (found: object with keys{}). If you meant to render a collection of children, use an array instead.
Here is my code that is currently working properly with domain inputs
import React from "react";
import axios from "axios";
export default class Form extends React.Component {
//making inputValue an empty string to allow the user input and work with that later
state = {
inputValue: "",
//making data an empty array to display later
data: [],
};
change = (e) => {
this.setState({
//when input is changed set the state of inputValue to the new value given from the form
[e.target.name]: e.target.value,
});
};
onSubmit = (e) => {
//create a constant variable that is called input to shorten up code when using it later
const input = document.getElementsByName("inputValue")[0].value;
//prevent page from refreshing when submit is pressed
e.preventDefault();
//appending the value the user input to trigger my API
var appendInput = "http://localhost:4000/api/whois/" + input;
var getForm = document.getElementById("inputForm");
getForm.action = appendInput;
// this.props.onSubmit(this.state.inputValue);
//at this point this.state is = my userInput
console.log(this.state.inputValue);
console.log("Before GET");
//rename the current state of inputValue into a variable to shorten the code up
const x = this.state.inputValue;
//this is where I use axios to make the call to the backend to then display data
console.log(`${encodeURIComponent(`${x}`)}`);
axios
.get(`http://localhost:4000/api/whois/${encodeURIComponent(`${x}`)}`)
.then((resp) => {
//when the request is successful I want to format the data from a JSON object into an array with key pair values
let k;
let newArr = [];
let newFormat = {};
for (k in resp.data) {
newFormat = { k: k, v: resp.data[k] };
console.log(k);
newArr.push(newFormat);
}
console.log("end", newArr);
console.log(typeof newArr);
console.log(typeof resp.data);
//setting state of data to an array from object
this.setState({ data: newArr });
console.log(this.state.data[0].k);
console.log(this.state.data[0].v);
})
.catch((err) => {
console.log(err);
});
console.log("Finished GET");
// this.setState({
// inputValue: ''
// });
};
render() {
return (
/* Create a form that allows the user to input either a domain or ip address */
<>
<form>
<input
id="inputForm"
name="inputValue"
placeholder="Domain or IP Address"
value={this.state.inputValue}
onChange={(e) => this.change(e)}
/>
<br />
<button className="submitBtn" onClick={(e) => this.onSubmit(e)}>
Submit
</button>
</form>
<br />
{/* THIS FINALLY WORKS! WOOOOOOO */}
{this.state.data.map((data) => (
<p key={data.id}>
{data.k} {data.v}
</p>
))}
</>
);
}
}

How to use multiple or two state values in one component or class file in react JS?

I am trying to use state to hide Semantic-UI Model form also to pass JSON to Customer class to add new customer. I am able to pass the value but in the end there is only one value.
When I start typing name, in console panel on 1st character name is empty "" and on 2nd character there is single "a"
In address
on create button click
when i click create button the name is empty"" and address has value.
import React from 'react';
import { Button, Form, Modal } from 'semantic-ui-react';
export default class AddCustomer extends React.Component {
constructor(props) {
super(props);
this.state = {
showCreateForm:false,
formData:{
name: '',
address: ''
}
}
this.handleChangeName = this.handleChangeName.bind(this);
this.handleChangeAddress = this.handleChangeAddress.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChangeName(event) {
const value = event.target.value;
console.log(value);
this.setState({formData:{name:value}});
//when i go to add input the formData is still empty name is empty.
//name: ""
//address: ""
console.log(this.state.formData);
}
handleChangeAddress(event) {
const value = event.target.value;
console.log(value);
this.setState({formData:{address:value}});
//name: "ram" but now there is no address in formData
console.log(this.state.formData);
}
handleSubmit(event) {
event.preventDefault();
////address: "aaaaa" now there no name in formData
console.log(this.state.formData);
this.setState({formData:{
name:this.state.name, address:this.state.address
}});
this.props.onAddFormSubmit(this.state.formData);
}
//On cancel button click close Create user form
closeCreateForm = () => {
this.setState({ showCreateForm: false })
}
//Open Create new Customer form
openCreateCustomer = () => {
this.setState({ showCreateForm: true })
}
render() {
return (
<div>
<Modal closeOnTriggerMouseLeave={false} trigger={
<Button color='blue' onClick={this.openCreateCustomer}>
New Customer
</Button>
} open={this.state.showCreateForm}>
<Modal.Header>
Create customer
</Modal.Header>
<Modal.Content>
<Form onSubmit={this.handleSubmit}>
<Form.Field>
<label>Name</label>
<input type="text" placeholder ='Name' name = "name"
value = {this.state.name}
onChange = {this.handleChangeName}/>
</Form.Field>
<Form.Field>
<label>Address</label>
<input type="text" placeholder ='Address' name = "address"
value = {this.state.address}
onChange = {this.handleChangeAddress}/>
</Form.Field>
<br/>
<Button type='submit' floated='right' color='green'>Create</Button>
<Button floated='right' onClick={this.closeCreateForm} color='black'>Cancel</Button>
<br/>
</Form>
</Modal.Content>
</Modal>
</div>
)
}
}
With these lines you are replacing your whole state with the value of one field:
Your initial state:
state = {
formData: {
name: "",
address: ""
}
}
Your state after this line:
this.setState({formData:{address:value}});
state = {
formData: {
address: ""
}
}
And with this line:
this.setState({formData:{name:value}});
state = {
formData: {
name: ""
}
}
As mentioned in the answer by Greg b, you have to change the value of the specific key and copy rest as they are. You can also use spread operator to copy rest of the unmodified fields easily.
this.setState({...this.state.formData, address: value})
will only change the address and copy rest of the state as it is. This comes handy when you have multiple fields in the state.
This is your state
formData:{
name: '',
address: ''
}
Change both these lines respectively
this.setState({formData:{address:value}});
this.setState({formData:{name:value}});
To
this.setState({formData:{name: this.state.formData.name, address:value}});`
this.setState({formData:{name:value, address: this.state.formData.address}});`
function handleChangeName(event) {
const value = event.target.value;
this.setState((prevState)=>({ formData: { name: value, address: prevState.formData.address }}),
()=>{ console.log(this.state.formData)});
}
function handleChangeAddress(event) {
const value = event.target.value;
this.setState((prevState)=>({ formData: { name: prevState.formData.name, address: value}}),
()=>{ console.log(this.state.formData)});
}
if you what to something next after setState() or state update, you have to use call back function.
State Updates are Merged
It looks like you aren't accessing the state correctly in the render(), I see you doing this.state.name and this.state.address rather than this.state.formData.nameand this.state.formData.address.
Also you probably shouldn't be storing your form data in a single state object, break it out into a field for each input, e.g.
this.state = {
showCreateForm: false,
name: "...",
address: "..."
}
You cannot update only part of an object in state, you need to update the whole thing. When you do this.setState({formData:{address:value}}); or are essentially setting this.state.formData.name to undefined, since the object is now going to only be { address: value }

Function CollectionReference.add() requires its first argument to be of type object, but it was: undefined

I want to store the image in firebase storage and pass its URL reference in firestore. So I'm able to upload an image in storage but unable to pass the image URL reference in cloud firestore.
import React , {Component} from 'react';
import fire from './../fire'
import Uploadimg from './Uploadimg'
class Adproduct extends Component{
geturl = (imurl) =>{
this.setState({
img:imurl
});
}
submit = e =>{
e.preventDefault()
var db= fire.firestore();
db.settings({
timestampsInSnapshots: true
});
db.collection('newproducts').add(this.State)
.then(res =>{
console.log(res.id)
this.props.submit()
})
.catch(err =>{
console.log('something went wrong',err)
})
}
takedata = e =>{
this.setState({
[e.target.name]: e.target.value
});
}
constructor(props){
super(props);
this.state ={
name:'',
productdetails:'',
size:'',
}
}
render() {
return (
<div className="container w3-padding">
<div className="row w3-padding">
<div className="col-md-6 w3-padding">
<h3 className="w3-tag w3-padding w3-center">Add New</h3>
<form className="w3-container" onSubmit={this.submit}>
<label className="w3-text-blue"><b>Name</b></label>
<input className="w3-input w3-border" type="text" name="name" value={this.state.name} onChange={this.takedata} required/>
<label className="w3-text-blue"><b>productdetails</b></label>
<input className="w3-input w3-border" type="text" name="productdetails" value={this.state.productdetails} onChange={this.takedata} required/>
<label className="w3-text-blue"><b>size available</b></label>
<input className="w3-input w3-border" type="text" name="size" value={this.state.size} onChange={this.takedata} required/>
<br/>
<Uploadimg geturl={this.geturl} />
<br/>
<button className="w3-btn w3-blue">Add</button>
</form>
</div>
</div>
</div>
);
}
}
export default Adproduct;
If the accepted answer doesn't help you, you might be having the same issue I was having.
I solved this by using the typescript spread operator:
add(wizard: Wizard): Promise<DocumentReference> {
return this.wizardCollection.add({...wizard});
}
Hope this helps you.
use
db.collection('newproducts').add({...this.State})
instead of
db.collection('newproducts').add(this.State)
it can happen if you forgot to create database in the project...
Its happen to me - I copy the project secret key - but forgot to actually create database in the project.... after I create the DB - it solved...
It simply works for me.
Go in Database -> Rules ->
Change allow read, write: if false; to true;
It can happen because either you have not created any database or have no permission to read/write on the database from Under Rules tab i.e,
{
"rules": {
".read": true,
".write": true
}
}
You should create the Firestore database, allow the read, write rules, and configure it to your project. Then you can use the spread operator as below to save.
saveStudent(student: Student) {
return new Promise<Student> ((resolve, reject) => {
this.fireStore.collection('students').add({...student}).then(res => {}, err => reject(err));
});
}
From the documentation
To use a custom class, you must define a FirestoreDataConverter function for your class. For example:
class City {
constructor (name, state, country ) {
this.name = name;
this.state = state;
this.country = country;
}
toString() {
return this.name + ', ' + this.state + ', ' + this.country;
}
}
// Firestore data converter
var cityConverter = {
toFirestore: function(city) {
return {
name: city.name,
state: city.state,
country: city.country
}
},
fromFirestore: function(snapshot, options){
const data = snapshot.data(options);
return new City(data.name, data.state, data.country)
}
}
Call your data converter with your write operation:
// Set with cityConverter
db.collection("cities").doc("LA")
.withConverter(cityConverter)
.set(new City("Los Angeles", "CA", "USA"));

React-Bootstrap form validation - Need one function per field?

I am using the React-Bootstrap forms. I have around 15 fields that need to be filled out in the form. Does this mean I need to have 15 validation functions (e.g validateName, validateDate etc.)?
How is this generally approached?
My data looks something like this:
state = {
person : {
name: '',
startDate: null,
...
...
active: null
}
}
Say for eg you have 2 input fields
state = {
person : {
name: '',
age: 0
},
nameError: null,
ageError: null
}
handleInput = e => {
const { person } = this.state;
person[e.target.name] = e.target.value;
this.setState({
person
});
}
handleSubmit = () => {
const { person } = this.state;
if(person.name === null){
this.setState({
nameError: 'Name is required',
ageError: null
});
}else if(person.age === 0){
this.setState({
ageError: 'Age is required',
nameError: null
});
}else{
//send the values to the backend
//also reset both nameError and ageError here
}
}
render(){
const { person, nameError, ageError } = this.state;
return(
<div>
<input type='text' name='name' value={person.name} onChange={e => this.handleInput(e)} />
{nameError}
<input type='number' name='age' value={person.age} onChange={e => this.handleInput(e)} />
{ageError}
<button value='Submit' onClick={this.handleSubmit} />
</div>
);
}
Please Let me know if you have further queries. Sorry if there are any typos I answered on my mobile

Resources