Updating Functional Component Local State Using Data From Redux State - reactjs

I'm building contact manager. When the user clicks the update button for a specific contact an action is dispatched and the "hotContact" property in the reducer's state is populated with an object. What I want is the fields of the ContactForm to be populated with the name and number of the "hotContact". However, despite the hotContact being loaded into the redux state my ContactForm component won't display the name and number of the hotContact. How can I proceed? This is what I have so far.
I tried calling setFormData in a conditional block to check if hotContact is present and loadingHotContact is false, but that just gives me an infinite re-render error.
import React, { useState } from 'react';
import { connect } from 'react-redux';
import { addContact, updateContact } from '../actions/contacts';
const ContactForm = ({
addContact,
updateContact,
contacts: { hotContact, loadingHotContact },
}) => {
const [formData, setFormData] = useState({
name:
hotContact === null && loadingHotContact
? ''
: hotContact.name,
number:
hotContact === null && loadingHotContact
? ''
: hotContact.number,
});
const onFormDataChange = (event) => {
setFormData({ ...formData, [event.target.name]: event.target.value });
};
const { name, number } = formData;
const handleSubmit = (event) => {
event.preventDefault();
const newContact = { name, number };
addContact(newContact);
console.log('Submit the form!');
setFormData({ name: '', number: '' });
};
const handleUpdateSubmit = (event) => {
event.preventDefault();
const updatedContact = { name, number };
updateContact(hotContact._id, updatedContact);
};
return !hotContact ? (
<form onSubmit={handleSubmit}>
<div>
Name{' '}
<input
type='text'
name='name'
value={name}
onChange={(event) => onFormDataChange(event)}
/>
</div>
<div>
Number{' '}
<input
type='text'
name='number'
value={number}
onChange={(event) => onFormDataChange(event)}
/>
</div>
<input type='submit' value='Add Contact' />
</form>
) : (
<form onSubmit={handleUpdateSubmit}>
<div>
Name{' '}
<input
type='text'
name='name'
value={name}
onChange={(event) => onFormDataChange(event)}
/>
</div>
<div>
Number{' '}
<input
type='text'
name='number'
value={number}
onChange={(event) => onFormDataChange(event)}
/>
</div>
<input type='submit' value='Apply Changes' />
</form>
);
};
const mapStateToProps = (state) => ({
contacts: state.contacts,
});
export default connect(mapStateToProps, { addContact, updateContact })(
ContactForm
);

This doesn't work because at the first renderer useState is initialized with the hotContact from the props, but when you receive the new value from the props the state doesn't update (that's how the useState hook works)
If you want to update your state you should use the useEffect hook:
const ContactForm = ({
addContact,
updateContact,
contacts: { hotContact, loadingHotContact },
}) => {
const [formData, setFormData] = useState({
name:
hotContact === null && loadingHotContact
? ''
: hotContact.name,
number:
hotContact === null && loadingHotContact
? ''
: hotContact.number,
});
useEffect(() => {
const {name, number} = props.hotContact;
setFormData({
name: name || '',
number: number || '',
});
// execute this
}, [hotContact]); // when hotContact changes
}
Also, I think you may simplify you assignment this way:
const {name, number} = props.hotContact;
setFormData({
name: name || '',
number: number || '',
});

Related

I can't catch the value of the checkbox input

I have a React form where I can't control the value of the checkbox input with the useState hook. I don't have this problem with other inputs.
I can't pass the checkbox input value to the AuthData object. When you click the "Sign in" button, the console should display an AuthData object with the fields { login: '', password: '', isRemember: '' }
import React from 'react'
import { useState } from 'react'
export const AuthForm = ({ handlers }) => {
const [authData, setAuthData] = useState({ login: '', password: '', isRemember: '' })
const changeValue = (event) => {
const { id, value } = event.target
setAuthData((prevAuthData) => ({ ...prevAuthData, [id]: value }))
}
const signIn = () => {
console.log(authData)
}
return (
<form onSubmit={(e) => e.preventDefault()}>
<input
type="text"
id="login"
placeholder="Login/E-mail/Phone"
value={authData.login}
onChange={changeValue}
/>
<input
type="password"
id="password"
placeholder="Password"
value={authData.password}
onChange={changeValue}
/>
<input
type="checkbox"
id="isRemember"
value={authData.isRemember}
onChange={changeValue}
/>
<button onClick={signIn}>Sign in</button>
</form>
)
}
When you change inputs values, their values must be passed to the authValue object.
With "login" and "password" inputs their values go into the authValue object, but with "isRemember" input this does not work. The value of checkbox inputs somehow does not get into the authValue object.
you can check the input type and get the checked value for checkbox from the event object as below
const changeValue = (event) => {
let { id, value, type, checked="" } = event.target;
if (type === "checkbox") {
value = checked;
}
setAuthData((prevAuthData) => ({ ...prevAuthData, [id]: value }));
};
You have to use the checked attribute on the checkbox input.
The value attribute is used, but you’ll have to modify it to ensure it sends true or false to the state object
I've added a snippet in response to your comment.
const {useState} = React
const AuthForm = ({ handlers }) => {
const [authData, setAuthData] = useState({ login: '', password: '', isRemember: false })
const changeValue = (event) => {
const { id, value } = event.target
setAuthData((prevAuthData) => ({ ...prevAuthData, [id]: value }))
}
const changeCheckbox = () => {
setAuthData((prevAuthData) => ({ ...prevAuthData, isRemember: !prevAuthData.isRemember }))
}
const signIn = () => {
console.log(authData)
}
console.log(authData);
return (
<form onSubmit={(e) => e.preventDefault()}>
<input
type="text"
id="login"
placeholder="Login/E-mail/Phone"
value={authData.login}
onChange={changeValue}
/>
<input
type="password"
id="password"
placeholder="Password"
value={authData.password}
onChange={changeValue}
/>
<input
type="checkbox"
id="isRemember"
checked={authData.isRemember}
onChange={changeCheckbox}
/>
<button onClick={signIn}>Sign in</button>
</form>
)
}
// Render it
ReactDOM.createRoot(
document.getElementById("root")
).render(
<AuthForm />
);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>

Losing focus on input onChange React

What I'm trying to achieve, it's to create each input as own component.
And I can't get how to fix thing when my app all the time rerendering when I'm pressing any key.
I know if I'll use controlledInputs -> so each input would have it own useState that would work. But the main idea to do that this way
import React, { useState } from 'react';
const ControlledInputs = () => {
const [person, setPerson] = useState({ firstName: '', email: '', age: '' });
const [people, setPeople] = useState([]);
const handleChange = (e) => {
const name = e.target.name;
const value = e.target.value;
setPerson({ ...person, [name]: value });
};
const handleSubmit = (e) => {
e.preventDefault();
if (person.firstName && person.email && person.age) {
const newPerson = { ...person, id: new Date().getTime().toString() };
setPeople([...people, newPerson]);
setPerson({ firstName: '', email: '', age: '' });
}
};
function FormControl({number, idname , type , text}){
return(
<div className='form-control'>
<label htmlFor={idname}>{text} : </label>
<input
type={type}
id={idname}
name={idname}
value={person[idname]}
onChange={(e) =>handleChange(e)}
/>
</div>
)
}
return (
<>
<article className='form'>
<form>
<FormControl idname={"firstName"} type={"text"} text={"First name"}/>
<FormControl type={"email"} idname={"email"} text={"Email"}/>
<FormControl type={"age"} idname={"age"} text={"Age"}/>
<button type='submit' className='btn' onClick={handleSubmit}>
add person
</button>
</form>
</article>
<article>
{people.map((person) => {
const { id, firstName, email, age } = person;
return (
<div key={id} className='item'>
<h4>{firstName}</h4>
<p>{email}</p>
<p>{age}</p>
</div>
);
})}
</article>
</>
);
};
export default ControlledInputs;
You need to define the FormControl outside of ControlledInputs, otherwise, React will recreate it and you lose focus as well as data.
And you need to pass value and handleChange as props in FormControl.
Here are codes you can refactor. Please note that the number you defined is not removed.
function FormControl({value, handleChange, idname , type , text}){
return(
<div className='form-control'>
<label htmlFor={idname}>{text} : </label>
<input
type={type}
id={idname}
name={idname}
value={value}
onChange={(e) =>handleChange(e)}
/>
</div>
)
}
Usage in form:
function FormControl({value, handleChange, idname , type , text}){
return(
<div className='form-control'>
<label htmlFor={idname}>{text} : </label>
<input
type={type}
id={idname}
name={idname}
value={value}
onChange={(e) =>handleChange(e)}
/>
</div>
)
}
const ControlledInputs = () => {
const [person, setPerson] = useState({ firstName: '', email: '', age: '' });
const [people, setPeople] = useState([]);
const handleChange = (e) => {
const name = e.target.name;
const value = e.target.value;
setPerson({ ...person, [name]: value });
};
const handleSubmit = (e) => {
e.preventDefault();
if (person.firstName && person.email && person.age) {
const newPerson = { ...person, id: new Date().getTime().toString() };
setPeople([...people, newPerson]);
setPerson({ firstName: '', email: '', age: '' });
}
};
return (
<>
<article className='form'>
<form>
<FormControl idname={"firstName"} type={"text"} text={"First name"} value={person['firstName']} handleChange={handleChange}/>
<FormControl type={"email"} idname={"email"} text={"Email"} value={person['email']} handleChange={handleChange}/>
<FormControl type={"age"} idname={"age"} text={"Age"} value={person['age']} handleChange={handleChange}/>
<button type='submit' className='btn' onClick={handleSubmit}>
add person
</button>
</form>
</article>
<article>
{people.map((person) => {
const { id, firstName, email, age } = person;
return (
<div key={id} className='item'>
<h4>{firstName}</h4>
<p>{email}</p>
<p>{age}</p>
</div>
);
})}
</article>
</>
);
};
That is because FormControl is defined inside the ControlledInputs component. So, on each rerender of the ControlledInputs, you create a new FormControl function and that means that React will treat it as a different component than the one in the previous render, and so it will lose focus, as the old one is considered unmounted.
Just define the FormControl outside the other one, and pass it what extra data you need as props, and you should be set.

Error TypeError: Cannot destructure property 'Active' of 'user' as it is undefined

I have looked over a few suggestions provided and tested but still getting the error. The error only happens when I create a new user and they are added to the below list and I go to edit them all other users in the list work fine accept the last one.
The page that is having the issue is when I click on the edit pencil and it tried to load the user to edit the information but again only on newly created users like the bottom user in this case.
I did find one that said to make it undefine || {} so I changed mine to be const { Active, FirstName, LastName, CollectorCode } = undefined || user; but the error was the same more or less TypeError: Cannot destructure property 'Active' of '(undefined || user)' as it is undefined.
UpdateUser.js
import React, { useState, useEffect } from "react";
import axios from "axios";
import { useParams } from "react-router-dom";
const UpdateUser = () => {
const { CollectorID } = useParams();
const [user, setUser] = useState({
Active: '',
FirstName: '',
LastName: '',
CollectorCode: ''
});
const { Active, FirstName, LastName, CollectorCode } = user;
const onInputChange = e => {
setUser({
...user, [e.target.name]: e.target.value,
Active: !Active });
};
useEffect(() => {
loadUser();
}, []);// eslint-disable-line react-hooks/exhaustive-deps
const loadUser = async () => {
const result = await axios.get(`http://localhost:5000/getCollectors/${CollectorID}`);
setUser(result.data[CollectorID - 1]);
// console.log(result.data[CollectorID - 1]);
};
const onSubmit = async e => {
e.preventDefault();
await axios.put(`http://localhost:5000/UpdateUser/${CollectorID}`, {
CollectorID: CollectorID,
Active: Active,
LastName: LastName,
CollectorCode: CollectorCode
});
console.log(CollectorID, Active, LastName, CollectorCode)
};
return (
<div className="previewWrapper">
<h1>Update Collector</h1>
{FirstName} {LastName} | {CollectorCode} - {CollectorID} {console.log(user)}
<form className="newUserForm" onSubmit={e => onSubmit(e)}>
<div className="newUserItem">
{/*Active or inactive User*/}
<label>Active</label>
<input
type='checkbox'
defaultValue={Active}
defaultChecked={Active}
onChange={e => onInputChange(e)}
/>
{/*Collector Last Name*/}
<label>Last Name</label>
<input
type="text"
placeholder="Last Name"
name="LastName"
defaultValue={LastName}
onChange={e => onInputChange(e)}
/>
{/*Collector Code First Initial Middle Initial Last Initial*/}
<label>Collector Code</label>
<input
type="text"
name="CollectorCode"
placeholder="Collector Code"
defaultValue={CollectorCode}
onChange={e => onInputChange(e)}
/>
<button className="newUserButton">Update Collector</button>
</div>
</form>
</div>
);
}
export default UpdateUser;

React js update form value using useState

I have a React functional component, a form accepting information for events. I need to send the filled in form info using a POST request. My formData state is not updating, I have tried different onChange functions to try and get this to work. Any ideas where I am going wrong?
`
import styled from 'styled-components';
import axios from 'axios';
import Input from './Input';
import react, {useState, useEffect} from "react";
import DateTimePicker from 'react-datetime-picker';
import TimePicker from "react-time-picker";
import DatePicker from "react-date-picker";
const url = 'http://localhost:5000/events/create'
const EventForm = (props)=> {
const [dateValue, onChangeDate] = useState(new Date());
const [timeValue, onChangeTime] = useState();
const [formData, setFormData] = useState({
firstName: '',
lastName: '',
contactEmail: '',
eventTitle: '',
eventDescription: '',
})
function onChange (e) {
let name = e.target.name ;
let value = e.target.value;
let formObj = { ...formData };
setFormData({ ...formData, [name]: value });
console.log(formData)
}
const body = {
firstName: formData.firstName,
lastName: formData.lastName,
contactEmail: formData.contactEmail,
eventTitle: formData.eventTitle,
eventDescription: formData.eventDescription,
eventDate: dateValue,
eventTime: timeValue,
}
const postFormData = async (e) => {
console.log(formData)
e.preventDefault()
await axios({
method: 'post',
url: url,
data: body,
})
.then((response) => {
console.log(response)
})
.catch(error => console.log(`Error: ${error}`));
}
// const postFormData = async (e) => {
// e.preventDefault()
// let newEvent = await fetch("http://localhost:5000/events/create",
// {
// method: "POST",
// headers: {
// 'Content-Type': 'application/json',
// 'Accept': 'application/json'
// },
// body: JSON.stringify(body)
// });
// newEvent = await newEvent.json();
// console.log(newEvent);
// }
useEffect(() => {
return () => {
console.log(formData.firstName)
}
})
return (
<form onSubmit={props.onSubmit}>
<>
{/* <DateTimePicker onChange={onChange} value={value} minDate={new Date()}/> */}
<StyledForm onSubmit={postFormData}>
<label>
First Name
</label>
<Input
name={"firstName"}
placeholder={"First Name"}
type={"text"}
value={formData.firstName}
onChange={(e) => setFormData({ ...formData, firstName: e.target.value})}
/>
<label>
Last Name
</label>
<Input
name={"lastName"}
placeholder={"Last Name"}
type={"text"}
onChange={onChange}
/>
<label>
Contact Email
</label>
<Input
name={"contactEmail"}
placeholder={"Email"}
type={"email"}
onChange={onChange}
/>
<label>
Event Date
</label>
<DatePicker onChange={onChangeDate} value={dateValue}/>
<label>
Event Time
</label>
<TimePicker onChange={onChangeTime} value={timeValue} />
<label>
Event Description
</label>
<Input
name={"eventTitle"}
placeholder={"Event Title"}
type={"text"}
onChange={onChange}
/>
<label>
Event Description
</label>
<Input
name={"eventDescription"}
placeholder={"Event Description"}
type={"text"}
width={"300px"}
height={"300px"}
onChange={onChange}
/>
<Input
name={"submit"}
type={"submit"}
value={"Create"}
/>
</form>
</>
);
}
export default EventForm;`
The formData state variable will not update immediately, your console.log will always print the original value before the update. Also when the next state is computed using the previous state like in your example, you should use the functional update version of setState:
const [formData, setFormData] = useState({
firstName: '',
lastName: '',
contactEmail: '',
eventTitle: '',
eventDescription: '',
})
function onChange (e) {
let name = e.target.name;
let value = e.target.value;
setFormData((currentFormData) => {
const nextFormData = {
...currentFormData,
[name]: value,
})
console.log(nextFormData)
return nextFormData;
);
}

React TypeScript: Multiple State updates but only first one gets applied

I made a sandBox here https://codesandbox.io/s/old-mountain-xl7nz when you don't full in any of the form inputs I expect to see 3 errors but I only get one. I don't understand why
import React, { ChangeEvent, useState } from 'react';
import { Link } from 'react-router-dom';
import loadingImg from '../images/loading.svg';
const Join: React.FC = () => {
const [state, setState] = useState({
email: '',
emailError: '',
fullName: '',
fullNameError: '',
loading: false,
password: '',
passwordError: '',
});
const {
email,
emailError,
fullName,
fullNameError,
password,
passwordError,
} = state;
const onChange = (event: ChangeEvent<HTMLInputElement>) => {
event.persist();
setState((prev) => ({
...prev,
[event.target.id]: event.target.value,
}));
};
const onSubmit = (event: React.FormEvent) => {
event.preventDefault();
if (validate('fullName') && validate('email') && validate('password')) {
console.log('FIRE FORM');
}
};
const onBlur = (event: ChangeEvent<HTMLInputElement>) => {
validate(event.target.id);
};
const validate = (id: string) => {
switch (id) {
case 'fullName':
if (!/^.{6,7}$/.test(fullName)) {
setState((prev) => ({ ...prev, fullNameError: 'err' }));
return false;
} else {
setState((prev) => ({ ...prev, fullNameError: '' }));
return true;
}
break;
case 'email':
if (!/\S+#\S+\.\S+/.test(email)) {
setState((prev) => ({ ...prev, emailError: 'err' }));
return false;
} else {
setState((prev) => ({ ...prev, emailError: '' }));
return true;
}
break;
default:
if (!/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[a-zA-Z\d]{8,}$/.test(password)) {
setState((prev) => ({ ...prev, passwordError: 'err' }));
return false;
} else {
setState((prev) => ({ ...prev, passwordError: '' }));
return true;
}
}
};
return (
<div className='join'>
<h2>JOIN</h2>
<h3>some subheading</h3>
<form onSubmit={onSubmit}>
<label>Name</label>
<input
type='text'
placeholder='Full name'
id='fullName'
value={fullName}
onChange={onChange}
onBlur={onBlur}
/>
{fullNameError}
<label>Email</label>
<input
type='text'
placeholder='Email address'
id='email'
value={email}
onChange={onChange}
onBlur={onBlur}
/>
{emailError}
<label>Password</label>
<input
type='password'
placeholder='Create a password'
id='password'
value={password}
onChange={onChange}
onBlur={onBlur}
/>
{passwordError}
<button color='primary'>
{!state.loading ? (
'Join Now'
) : (
<img src={loadingImg} alt='loadingd' className='loading' />
)}
</button>
<div className='join--terms'>
By joining, you agree to our
<Link to={{ pathname: '/terms' }}> Terms of Service</Link> and
<Link to={{ pathname: '/terms' }}> Privacy Policy</Link>
</div>
</form>
</div>
);
};
export { Join };
your conditions are not evaluating when first condition is false...
so you better to do something like this....
const onSubmit = (event: React.FormEvent) => {
event.preventDefault();
const fullNameValidation = validate('fullName')
const emailValidation = validate('email')
const passwordValidation = validate('password')
if (fullNameValidation && emailValidation && passwordValidation) {
console.log('FIRE FORM');
}
};

Resources