So I was learning my React course today on Udemy and came across an error where the react app renders [object Object] instead of the JSX (which is supposed to be an empty input box), also, since the input box has onChange method, The input value is also unable to change. Here is the image of the application and also the code given below:-
import React, { useState, useEffect, useReducer } from "react";
import Card from "../UI/Card/Card";
import classes from "./Login.module.css";
import Button from "../UI/Button/Button";
const emailReducer = (state, action) => {
if(action.type === 'USER_INPUT'){
return {value: action.value, isValid: action.value.includes('#')};
}
if(action.type === 'INPUT_BLUR'){
return {value: state.value, isValid: state.value.includes('#')};
}
return { value: '', isValid: false };
};
const Login = (props) => {
// const [enteredEmail, setEnteredEmail] = useState("");
// const [emailIsValid, setEmailIsValid] = useState();
const [enteredPassword, setEnteredPassword] = useState("");
const [passwordIsValid, setPasswordIsValid] = useState();
const [formIsValid, setFormIsValid] = useState(false);
const [emailState, dispatchEmail] = useReducer(emailReducer, {
value: '',
isValid: false
});
useEffect(() => {
console.log("EFFECT RUNNING");
return () => {
console.log("EFFECT CLEANUP");
};
}, []);
const emailChangeHandler = (event) => {
dispatchEmail({type: 'USER_INPUT', value: event.target.value});
setFormIsValid(
emailState.isValid && enteredPassword.trim().length > 6
);
};
const passwordChangeHandler = (event) => {
setEnteredPassword(event.target.value);
setFormIsValid(
emailState.isValid && event.target.value.trim().length > 6
);
};
const validateEmailHandler = () => {
dispatchEmail({type: 'INPUT_BLUR'});
};
const validatePasswordHandler = () => {
setPasswordIsValid(enteredPassword.trim().length > 6);
};
const submitHandler = (event) => {
event.preventDefault();
props.onLogin(emailState.value, enteredPassword);
};
return (
<Card className={classes.login}>
<form onSubmit={submitHandler}>
<div
className={`${classes.control} ${
emailState.isValid === false ? classes.invalid : ""
}`}
>
<label htmlFor="email">E-Mail</label>
<input
id="email"
value={emailState}
onChange={emailChangeHandler}
onBlur={validateEmailHandler}
/>
</div>
<div
className={`${classes.control} ${
passwordIsValid === false ? classes.invalid : ""
}`}
>
<label htmlFor="password">Password</label>
<input
type="password"
id="password"
value={enteredPassword}
onChange={passwordChangeHandler}
onBlur={validatePasswordHandler}
/>
</div>
<div className={classes.actions}>
<Button type="submit" className={classes.btn} disabled={!formIsValid}>
Login
</Button>
</div>
</form>
</Card>
);
};
export default Login;
Your emailState is an object with the following shape:
{ value: string, isValid: boolean }
So in your input field you need to use the value attribute:
<input
id="email"
value={emailState.value}
onChange={emailChangeHandler}
onBlur={validateEmailHandler}
/>
You are passing the entire emailState object to the input when you likely want the nested value property. You can pass emailState.value to the input's value prop, or destructure the state beforehand and pass value directly.
const [{ isValid, value }, dispatchEmail] = useReducer(emailReducer, {
value: '',
isValid: false
});
...
<input
id="email"
value={value}
onChange={emailChangeHandler}
onBlur={validateEmailHandler}
/>
Related
Hi i'm newbie learning to code and start with Js, Reactjs. I convert my code from class component to functional component and then my validation stop working. i have looking around but yet to find answer, can someone help? ^^"
export default function AddComment() {
const [formValue, setFormValue] = useState ({
rating: '5',
yourname: '',
comment: '',
})
const {rating, yourname, comment} = formValue
const [isModalOpen, setIsModalOpen] = useState(false)
const [touched, setTouched] = useState({yourname: false})
const handleInputChange = (event) => {
const {name, value} = event.target
setFormValue((prevState) => {
return {
prevState,
[name]:value,
};})
}
const handleBlur = (field) => (evt) => {
setTouched({
touch: {...touched, [field]: true }
})
};
function validate(yourname) {
const errors = {
yourname: ''
};
if (touched.yourname && yourname.length <2)
errors.yourname = 'Your name should be atleast 2 characters';
else if (touched.yourname && yourname.length >= 10)
errors.yourname = 'Your name should be less than 10 characters';
return errors
}
const errors = validate(yourname)
return (
<ModalBody>
<Form onSubmit={handleSubmit}>
<FormGroup row>
<Label htmlFor='yourname' md = {12}>Your Name</Label>
<Col md = {12}>
<Input type= 'text' id= 'yourname' name= 'yourname' value= {yourname}
valid={errors.yourname === ''} invalid= {errors.yourname !== ''}
onBlur= {handleBlur('yourname')} onChange= {handleInputChange}/>
<FormFeedback>{errors.yourname}</FormFeedback>
</Col>
</FormGroup>
</Form>
</ModalBody>
)
}
Not sure this will solve your problem, but notice on handleInputChange you are not spreading the previous state, so the object you're setting state with looks like:
{
prevState: {...the actual previous state},
[name]: ...what you wanted to change
}
Im checking to see if the register forms passwords match, and when they do, something changes. but its happening on 1 "onChange" too late. Ex. User enters "DOG" as the password. when the retype it in the 2nd input, "DOG" doesnt work. but it does if they enter another character or delete one character (Ex. "DOGX" or deleting "G" so its "DO")
import React, { useState } from "react";
import { useHistory } from "react-router-dom";
import "./register.css";
function RegisterBoard() {
const history = useHistory();
const [register, changeRegister] = useState({
password: false,
repeatPassword: false,
});
const [info, changeInfo] = useState({
password: "",
repeatPassword: "",
});
const changeValue = (e) => {
const { name, value } = e.target;
changeInfo((prev) => {
return {
...prev,
[name]: value,
};
});
};
const input = (e) => {
const target = e.target.dataset.name;
if (target != "repeatPassword") {
changeRegister({
...register,
[target]: true,
});
} else {
if (info.password != info.repeatPassword) {
changeRegister({
...register,
repeatPassword: false,
});
} else {
changeRegister({
...register,
repeatPassword: true,
});
}
}
};
return (
<div className="registration-form">
<form>
<div>
<input
name="password"
data-name="password"
onChange={(e) => {
changeValue(e);
input(e);
}}
className="password"
type="password"
placeholder="ENTER YOUR PASSWORD HERE"
/>
<div className="animated-button">
</div>
</div>
<div>
<input
id="pwd"
name="repeatPassword"
data-name="repeatPassword"
onChange={(e) => {
changeValue(e);
input(e);
}}
className="repeat-password"
type="password"
placeholder="REPEAT YOUR PASSWORD HERE"
/>
</div>
</div>
</form>
</div>
);
}
export default RegisterBoard;
I guess this is because you are calling both 'changeValue' and 'input' functions within the inputs onChange attribute. Since they are firing at the same time, 'input' is not using the most recent value for 'info', because 'changeValue' hasn't set the new state yet.
Either call the input function within a useEffect hook which is dependent on changes to 'info's' state, or use e.target.value instead of info's state within the 'input' function to compare info.password != info.repeatPassword
EDIT: here is the useEffect way, it simplifies it and you can remove your input function completely: https://codesandbox.io/s/jolly-khorana-8s63b?file=/src/App.js
import React, { useState, useEffect } from "react";
import "./styles.css";
function RegisterBoard() {
const [register, changeRegister] = useState({
password: false,
repeatPassword: false
});
const [info, changeInfo] = useState({
password: "",
repeatPassword: ""
});
const changeValue = (e) => {
const { name, value } = e.target;
changeInfo((prev) => {
return {
...prev,
[name]: value
};
});
};
useEffect(() => {
let password = false;
let repeatPassword = false;
if (info.password !== "") {
password = true;
if (info.password === info.repeatPassword) {
repeatPassword = true;
}
}
changeRegister({ password, repeatPassword });
}, [info]);
return (
<div className="registration-form">
<form>
<div>
<input
name="password"
data-name="password"
onChange={changeValue}
className="password"
type="password"
placeholder="ENTER YOUR PASSWORD HERE"
/>
<div className="animated-button"></div>
</div>
<div>
<input
id="pwd"
name="repeatPassword"
data-name="repeatPassword"
onChange={changeValue}
className="repeat-password"
type="password"
placeholder="REPEAT YOUR PASSWORD HERE"
/>
</div>
</form>
<div>{info.password}</div>
<div>{info.repeatPassword}</div>
<div>{register.repeatPassword ? "match" : "don't match"}</div>
</div>
);
}
export default function App() {
return (
<div className="App">
<RegisterBoard />
</div>
);
}
You're definitely going to want to implement a useEffect here to update the UI every time the password & repeatPassword state changes, to ensure that after the last character is typed that you get the full password. Inside the useEffect is where you'll write your conditional logic. What I provided is just a good example...
import React, { useState, useEffect } from "react";
import { useHistory } from "react-router-dom";
import "./register.css";
function RegisterBoard() {
const history = useHistory();
const [password, setPassword] = useState('')
const [repeatPassword, setRepeatPassword] = useState('')
//const [register, changeRegister] = useState(false);
const changeValue = (e) => {
const { name, value } = e.target.value;
const input = (e) => {
const target = e.target.dataset.name;
if (target != "repeatPassword") {
changeRegister({
...register,
[target]: true,
});
} else {
if (info.password != info.repeatPassword) {
changeRegister({
...register,
repeatPassword: false,
});
} else {
changeRegister({
...register,
repeatPassword: true,
});
}
}
};
useEffect(() => {
if((password !== "" && repeatPassword !== "") && (password !==
repeatPassword)){
console.log("PASSWORDS DO NOT MATCH!!!")
}
console.log(password, repeatPassword)
}, [password, repeatPassword])
return (
<div className="registration-form">
<form>
<div>
<input
name="password"
data-name="password"
onChange={(e) => changeValue(e)}
className="password"
type="password"
placeholder="ENTER YOUR PASSWORD HERE"
/>
<div className="animated-button">
</div>
</div>
<div>
<input
id="pwd"
name="repeatPassword"
data-name="repeatPassword"
onChange={(e) => changeValue(e)}
className="repeat-password"
type="password"
placeholder="REPEAT YOUR PASSWORD HERE"
/>
</div>
</div>
</form>
</div>
);
}
export default RegisterBoard;
I created some logic that is reusable code anywhere. I have an input.jsx component for handling input elements, and use-form.js custom hook for managing all of the stuff and I have a basic-form component for creating form logic. Inside of basic-form component, I'm calling the custom hook many times. That is tedious and quite repetitive* so I wanna create two reusable invoking custom hooks in the input.jsx component
input.component.jsx
const Input = (props) => {
return (
<div className={`form-control ${props.error && 'invalid'}`}>
<label htmlFor={props.id}>{props.label}</label>
<input
type={props.type}
id={props.id}
value={props.value}
onChange={props.changeHandler}
onBlur={props.blurHandler}
/>
{props.error && <p className="error-text">{props.label} must be entered!</p>}
</div>
);
};
use-input.js
import { useReducer } from 'react';
const inputReducer = (state, action) => {
switch (action.type) {
case 'INPUT':
return { value: action.value, isTouched: state.isTouched };
case 'BLUR':
return { isTouched: true, value: state.value };
case 'RESET':
return { value: '', isTouched: false };
}
return state;
};
export const useInput = (validateValue) => {
const [state, dispatch] = useReducer(inputReducer, {
value: '',
isTouched: false,
});
const valueIsValid = validateValue(state.value);
const error = !valueIsValid && state.isTouched;
const valueChangeHandler = (e) => {
dispatch({ type: 'INPUT', value: e.target.value });
};
const inputBlurHandler = () => {
dispatch({ type: 'BLUR' });
};
const reset = () => {
dispatch({ type: 'RESET' });
};
return {
value: state.value,
isValid: valueIsValid,
error,
valueChangeHandler,
inputBlurHandler,
reset,
};
};
basic-form.component.jsx
import { useInput } from '../hooks/use-input';
import Input from './input.component';
const BasicForm = () => {
const {
value: name,
isValid: nameIsValid,
error: nameHasError,
valueChangeHandler: nameChangeHandler,
inputBlurHandler: nameBlurHandler,
reset: nameReset,
} = useInput((value) => value.trim() !== '');
const {
value: surname,
isValid: surnameIsValid,
error: surnameHasError,
valueChangeHandler: surnameChangeHandler,
inputBlurHandler: surnameBlurHandler,
reset: surnameReset,
} = useInput((value) => value.trim() !== '');
const {
value: email,
isValid: emailIsValid,
error: emailHasError,
valueChangeHandler: emailChangeHandler,
inputBlurHandler: emailBlurHandler,
reset: emailReset,
} = useInput((value) => /^\S+#\S+\.\S+$/.test(value));
let formIsValid = false;
if (nameIsValid && surnameIsValid && emailIsValid) {
formIsValid = true;
}
const formSubmitHandler = (e) => {
e.preventDefault();
nameReset();
surnameReset();
emailReset();
};
return (
<form onSubmit={formSubmitHandler}>
<div className="control-group">
<Input
id="name"
label="First Name"
type="text"
error={nameHasError}
value={name}
changeHandler={nameChangeHandler}
blurHandler={nameBlurHandler}
/>
<Input
id="surname"
label="Last Name"
type="text"
error={surnameHasError}
value={surname}
changeHandler={surnameChangeHandler}
blurHandler={surnameBlurHandler}
/>
<Input
id="email"
label="Email"
type="email"
error={emailHasError}
value={email}
changeHandler={emailChangeHandler}
blurHandler={emailBlurHandler}
/>
</div>
<div className="form-actions">
<button disabled={!formIsValid}>Submit</button>
</div>
</form>
);
};
export default BasicForm;
The problem with that is we must return two values from input.jsx to basic-form.jsx. The first value shows the property isValid or not. Second, a function for reseting every individual input element. Also, we must receive a function that validates the logic from the parent to the children components. How can we solve this problem? I think we should use useRef, useImperativeHandle and forwardRef but how?
Please vote up for more people to see.
const { useReducer } = React
const InputWithLabelAbove = ({
labelText,
id,
onChange,
pattern,
value,
}) => {
return (
<label htmlFor={id}>
{labelText}
<div>
<input
type="text"
id={id}
pattern={pattern}
value={value}
onChange={onChange}
/>
</div>
</label>
)
}
const MemoInputWithLabelAbove = React.memo(InputWithLabelAbove)
const Component = () => {
// calc object
const calculatorObj = {
tax: '',
water: '',
energy: '',
internet: '',
transport: '',
food: '',
education: '',
childcare: '',
otherInsurance: '',
otherCosts: ''
}
// state
const inputValues = {
total: '',
showCalculator: false,
calculatedTotal: 0,
calc: {
...calculatorObj
}
}
// reducer for form states
const [values, setValues] = useReducer(
(state, newState) => ({...state, ...newState}),
inputValues
)
// on change function to handle field states.
const handleChange = React.useCallback((e, type) => {
const { value, id } = e.target
console.log('onchange')
const re = /^[0-9\b]+$/
const converted = !re.test(value) || value.length === 0 ? '' : parseInt(value, 10)
if (type === 'calculator') {
const obj = {
...values.calc,
[id]: converted
}
setValues({ calc: { ...obj }})
}
}, [values.calc])
const calcLabelArr = ['Council tax', 'Water', 'Energy (gas and/or electricity)', 'Internet', 'Transport', 'Food', 'Children\'s education', 'Childcare', 'Other insurance', 'Other essential costs']
return (
<div
style={{ width: '60%', marginBottom: '20px', position: 'relative' }}
>
{ Object.entries(values.calc).map((i, index) => {
return <div key={calcLabelArr[index]}>
<MemoInputWithLabelAbove
id={i[0]}
type="text"
labelText={calcLabelArr[index]}
onChange={(e) => handleChange(e, 'calculator')}
value={i[1]}
/>
</div>
}
)}
</div>
)
}
ReactDOM.render(
<Component />,
document.getElementById('reactBind')
)
<script crossorigin src="https://unpkg.com/react#17/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#17/umd/react-dom.development.js"></script>
<div id="reactBind"></div>
Below is the rendering of the inputs using an array (with 10 elements) so 10 input elements are rendered.
// calculator object typically populated but for this example its empty for ease.
const calcLabelArr = []
// function to return calculator fields
const buildView = () => {
return (
<Col
xs="12"
md={{ span: 6, offset: 3 }}
style={{ marginBottom: '20px', position: 'relative' }}
>
{ Object.entries(values.calc).map((i, index) => {
return <div key={calcLabelArr[index]}>
<InputWithLabelAbove
id={i[0]}
type="text"
labelPosition="top"
labelText={calcLabelArr[index]}
onChange={(e) => handleChange(e, 'calculator')}
value={i[1]}
/>
</div>
}
)}
</Col>
)
}
Below is the onChange function used to set that state of each input.
const handleChange = React.useCallback((e, type) => {
const { value, id } = e.target
const re = /^[0-9\b]+$/
const converted = !re.test(value) || isEmpty(value) ? '' : parseInt(value, 10)
if (type === 'calculator') {
const obj = {
...values.calc,
[id]: converted
}
setValues({ calc: { ...obj }})
} else {
setValues({
total: converted,
})
}
}, [values.calc])
Below is the component that is memoized.
import React from 'react'
import { join } from 'lodash'
import { Label, StyledInput, Red } from './style'
export type IProps = {
labelPosition: string,
labelText: string,
id: string,
hasErrored?: boolean,
onChange: () => void,
dataUxId?: string,
pattern?: string,
sessioncamHide?: boolean,
sessioncamClassList?: string | string[],
value?: string,
validation?: boolean,
}
const InputWithLabelAbove: React.FC<IProps> = ({
labelPosition,
labelText,
id,
hasErrored,
onChange,
dataUxId,
pattern,
sessioncamHide,
sessioncamClassList,
value,
validation,
}) =>
(
<Label hasErrored={hasErrored} labelPosition={labelPosition} htmlFor={id}>
{labelText && (<span>{labelText}{validation && (<Red>*</Red>)}</span>)}
<div>
<StyledInput
type="text"
id={id}
hasErrored={hasErrored}
dataUxId={`InputText_${dataUxId}`}
pattern={pattern}
labelPosition={labelPosition}
value={value}
onInput={onChange}
onChange={onChange}
/>
</div>
</Label>
)
export const MemoInputWithLabelAbove = React.memo(InputWithLabelAbove)
As you can see, it isn't the key I don't think that is causing the re-render, my input component is memoized and the onChange is using a callback, however on using the react profiler, every onChange re-renders all my input components. Could someone elaborate to why this is?
One thing that jumps out is this property on the input component:
onChange={(e) => handleChange(e, 'calculator')}
Even though handleChange is memoized, you're creating a new arrow function every time to call the memoized function. So even if the input is memoized, it's seeing a new onChange every time.
You'd need to pass a stable function to avoid re-rendering, for instance:
const onChange = React.useCallback(
e => handleChange(e, "calculator"),
[handleChange]
);
and then
onChange={onChange}
(It wasn't clear to me where you're defining handleChange; if it's within the component that's rendering these inputs, you can probably combine that definition with the above in a single useCallback or possible useMemo if it's multiple callbacks. Although small pieces are good too.)
Hi i found an answer to this for a single field form... but what if we have a form with multiple field?
this is fine for disabling it if you have 1 field but it does not work when you want to disable it based on many fields:
getInitialState() {
return {email: ''}
},
handleChange(e) {
this.setState({email: e.target.value})
},
render() {
return <div>
<input name="email" value={this.state.email} onChange={this.handleChange}/>
<button type="button" disabled={!this.state.email}>Button</button>
</div>
}
})
Here is a basic setup for form validation:
getInitialState() {
return {
email: '',
text: '',
emailValid: false, // valid flags for each field
textValid: false,
submitDisabled: true // separate flag for submit
}
},
handleChangeEmail(e) { // separate handler for each field
let emailValid = e.target.value ? true : false; // basic email validation
let submitValid = this.state.textValid && emailvalid // validate total form
this.setState({
email: e.target.value
emailValid: emailValid,
submitDisabled: !submitValid
})
},
handleChangeText(e) { // separate handler for each field
let textValid = e.target.value ? true : false; // basic text validation
let submitValid = this.state.emailValid && textvalid // validate total form
this.setState({
text: '',
textValid: textValid,
submitDisabled: !submitValid
})
},
render() {
return <div>
<input name="email" value={this.state.email} onChange={this.handleChangeEmail}/>
<input name="text" value={this.state.text} onChange={this.handleChangeText}/>
<button type="button" disabled={this.state.submitDisabled}>Button</button>
</div>
}
})
In a more elaborate setup, you may want to put each input field in a separate component. And make the code more DRY (note the duplication in the change handlers).
There are also various solutions for react forms out there, like here.
I would take a little bit different way here...
Instead of setting submitDisabled in every single onChange handler I would hook into lifecycle method to listen to changes.
To be exact into componentWillUpdate(nextProps, nextState). This method is invoked before every change to component - either props change or state change. Here, you can validate your form data and set flag you need - all in one place.
Code example:
componentWillUpdate(nextProps, nextState) {
nextState.invalidData = !(nextState.email && nextState.password);
},
Full working fiddle https://jsfiddle.net/4emdsb28/
This is how I'd do it by only rendering the normal button element if and only if all input fields are filled where all the states for my input elements are true. Else, it will render a disabled button.
Below is an example incorporating the useState hook and creating a component SubmitButton with the if statement.
import React, { useState } from 'react';
export function App() {
const [firstname, setFirstname] = useState('');
const [lastname, setLastname] = useState('');
const [email, setEmail] = useState('');
function SubmitButton(){
if (firstname && lastname && email){
return <button type="button">Button</button>
} else {
return <button type="button" disabled>Button</button>
};
};
return (
<div>
<input value={email} onChange={ e => setEmail(e.target.value)}/>
<input value={firstname} onChange={ e => setFirstname(e.target.value)}/>
<input value={lastname} onChange={ e => setLastname(e.target.value)}/>
<SubmitButton/>
</div>
);
};
This might help. (credits - https://goshakkk.name/form-recipe-disable-submit-button-react/)
import React from "react";
import ReactDOM from "react-dom";
class SignUpForm extends React.Component {
constructor() {
super();
this.state = {
email: "",
password: ""
};
}
handleEmailChange = evt => {
this.setState({ email: evt.target.value });
};
handlePasswordChange = evt => {
this.setState({ password: evt.target.value });
};
handleSubmit = () => {
const { email, password } = this.state;
alert(`Signed up with email: ${email} password: ${password}`);
};
render() {
const { email, password } = this.state;
const isEnabled = email.length > 0 && password.length > 0;
return (
<form onSubmit={this.handleSubmit}>
<input
type="text"
placeholder="Enter email"
value={this.state.email}
onChange={this.handleEmailChange}
/>
<input
type="password"
placeholder="Enter password"
value={this.state.password}
onChange={this.handlePasswordChange}
/>
<button disabled={!isEnabled}>Sign up</button>
</form>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<SignUpForm />, rootElement);
export default function SignUpForm() {
const [firstName, onChangeFirstName] = useState("");
const [lastName, onChangeLastName] = useState("");
const [phoneNumber, onChangePhoneNumber] = useState("");
const areAllFieldsFilled = (firstName != "") && (lastName != "") && (phoneNumber != "")
return (
<Button
title="SUBMIT"
disabled={!areAllFieldsFilled}
onPress={() => {
signIn()
}
}
/>
)
}
Similar approach as Shafie Mukhre's!