I am working on building out a radio component and when checked I want the values from the radio button to pass when set onBlur. I am trying to pass a function to the onBlur but I am not seeing the values on my Form Submit action.
const [isChecked, setIsChecked] = React.useState<boolean>(defaultValue);
const onValue = React.useMemo(() => {
if (!isChecked) return [`${label}, ${description}`];
}, [onChange]);
const checkedValue = React.useCallback(
(e: React.ChangeEvent<HTMLInputElement>) =>
onChange?.(onValue, e.target.value),
[onChange]
);
return (
<div className="space-y-5">
<div className="relative flex items-start">
<div key={id} className="flex h-5 items-center">
<input
id={`${label}-id`}
name={label}
type="radio"
className="h-4 w-4 border-gray-300 text-indigo-600 focus:ring-indigo-500"
onBlur={checkedValue)}
onChange={() => setIsChecked(!isChecked)}
/>
</div>
<div className="ml-3 text-sm">
<label
htmlFor={`${label}-id`}
className="block font-medium text-gray-700"
>
{label}
</label>
<p id={`${label}-description`} className="text-gray-500">
{description}
</p>
</div>
</div>
</div>
);
};
Related
The problem with the onChange event is, it is providing the value but with one character less.
Example : if I type "overflow" in the input box, the provided value will be "overflo" (without w).
It's not registering the last character input.
How can i get the full text from the input field. Is there any other way to solve this problem.
import React, {useState} from "react";
function Average() {
const [totalVal, setTotalVal] = useState("");
const [shares, setShares] = useState("");
const handleKeyDown = (event) => {
if (event.key === 'Enter') {
setTotalVal(totalVal + "+");
}
};
const calcAvg = (event) => {
let avg = eval(totalVal) / shares;
document.getElementById("avgBox").innerHTML = ": ₹" + avg.toFixed(2);
event.preventDefault();
};
return (
<>
<div>
<div className="my-4">
<label className="block">
<input
type="number"
id="totalShares"
className="mt-1 px-3 py-2 bg-white border shadow-sm border-slate-300 placeholder-slate-500 focus:outline-none focus:border-slate-300 focus:ring-slate-300 block w-full rounded-md sm:text-sm focus:ring-1"
placeholder="Enter number of stocks"
value={shares}
onChange={(event) => setShares(event.target.value)}
/>
</label>
</div>
<div className="my-4">
<label className="block">
<input
type="text"
id="stockVal"
value={totalVal}
className="mt-1 px-3 py-2 bg-white border shadow-sm border-slate-300 placeholder-slate-400 focus:outline-none focus:border-slate-300 focus:ring-slate-300 block w-full rounded-md sm:text-sm focus:ring-1"
placeholder="Enter price of every stock with + in between"
autoComplete="off"
onChange={(event) => {setTotalVal(event.target.value); calcAvg()}}
onKeyDown={handleKeyDown}
/>
</label>
</div>
<div
className="p-2 my-8 unselectable cursor-pointer flex w-full justify-center bg-green-400 hover:bg-green-500 text-white font-medium rounded"
onClick={calcAvg}>
Average Price<div id="avgBox"></div>
</div>
</div>
</>
);
}
export default Average;
Here is an example of getting input values with useRef() hook.
const ref = useRef();
const handleChange = () => {
console.log(ref.current.value);
};
return (
<div className="App">
<h2>I'm trying to get input value</h2>
<input
type="text"
ref={ref}
onChange={handleChange}
placeholder="type something..."
/>
</div>
);
CodeSandbox:
https://codesandbox.io/s/kind-framework-ge6w2e?file=/src/App.js
The problem is that you setTotalVal and immediately call calcAvg. Since setState is sort of async this function won't have the updated value until the next render.
You can fix this by using another state in combination with a useEffect. We'll create averageValue state to store the average value. Then we add the useEffect to run whenever the totalVal or the shares change. Instead of the document.getElementBy we can simply display the average value the React way.
I removed the calcAvg since we don't need it anymore when using the new functionality.
Note: It is not recommended to manipulate the DOM using document.getElementBy....
import React, { useState, useEffect } from "react";
function Average() {
const [totalVal, setTotalVal] = useState("");
const [shares, setShares] = useState("");
const [averageValue, setAverageValue] = useState(0);
const handleKeyDown = (event) => {
if (event.key === "Enter") {
setTotalVal(totalVal + "+");
}
};
useEffect(() => {
const avg = eval(totalVal) / shares;
setAverageValue(avg);
}, [totalVal, shares]);
return (
<>
<div>
<div className="my-4">
<label className="block">
<input
type="number"
id="totalShares"
className="mt-1 px-3 py-2 bg-white border shadow-sm border-slate-300 placeholder-slate-500 focus:outline-none focus:border-slate-300 focus:ring-slate-300 block w-full rounded-md sm:text-sm focus:ring-1"
placeholder="Enter number of stocks"
value={shares}
onChange={(event) => setShares(event.target.value)}
/>
</label>
</div>
<div className="my-4">
<label className="block">
<input
type="text"
id="stockVal"
value={totalVal}
className="mt-1 px-3 py-2 bg-white border shadow-sm border-slate-300 placeholder-slate-400 focus:outline-none focus:border-slate-300 focus:ring-slate-300 block w-full rounded-md sm:text-sm focus:ring-1"
placeholder="Enter price of every stock with + in between"
autoComplete="off"
onChange={(event) => {
setTotalVal(event.target.value);
}}
onKeyDown={handleKeyDown}
/>
</label>
</div>
<div className="p-2 my-8 unselectable cursor-pointer flex w-full justify-center bg-green-400 hover:bg-green-500 text-white font-medium rounded">
Average Price: ₹ {averageValue.toString()}
</div>
</div>
</>
);
}
Side note: Although eval works, I suggest you try and take a look into storing your stock prices and amounts within some kind of array. This way you can, in the future, for example list all of you stocks.
I'm trying to make simple form using React,Typescript, have done some inputs and while adding them with onChange to one main state, first submit always returns empty. Why?
import React from "react";
import { useState } from "react";
import { Form } from "../components/Form/Form";
import { Navbar } from "../components/Navbar";
export const MainPage = () => {
const [formData, setFormData] = useState([]);
return (
<div className="w-full flex">
<Navbar />
<Form formData={formData} setFormData={setFormData} />
</div>
);
};
import React from "react";
import { useState } from "react";
interface Props{
formData: string[];
setFormData: React.Dispatch<React.SetStateAction<any>>;
}
export const Form: React.FC<Props> = ({setFormData, formData}) => {
const [organization, setOrganization] = useState<string>("");
const [title, setTitle] = useState<string>("");
const [firstName, setFirstName] = useState<string>("");
const [lastName, setLastName] = useState<string>("");
const [languages, setLanguages] = useState<string>("");
const [employmentType, setEmploymentType] = useState<string>("");
const [profession, setProfession] = useState<string>("");
const [proficiency, setProficiency] = useState<string>("");
const submitData = (e: any) => {
const data = {
organization,
title,
firstName,
lastName,
languages,
employmentType,
profession,
proficiency,
};
setFormData((prevData: any) => [...prevData, data]);
console.log(formData)
}
return (
<div className="w-full">
<div className="w-3/4 mx-auto mt-5">
<h1 className="text-2xl">About you</h1>
<div className="top flex mt-16 gap-8">
<div>
<h2 className="text-xl font-bold">Personal info</h2>
<p className="text-sm font-extralight">
Provide your personal info
</p>
</div>
<div className="flex flex-col gap-5">
<div>
<input
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setOrganization(e.target.value);
}}
className="w-full bg-gray-200 p-2 rounded-md placeholder:text-sm placeholder:text-black"
placeholder="Organization"
></input>
</div>
<div className="flex gap-3">
<select
onChange={(e: React.ChangeEvent<HTMLSelectElement>) => {
setTitle(e.target.value);
}}
className="bg-gray-200 p-2 rounded-md placeholder:text-sm placeholder:text-black"
>
<option value="Title">Title</option>
<option value="Mr">Mr</option>
<option value="Mrs">Mrs</option>
<option value="Miss">Miss</option>
<option value="Ms">Ms</option>
</select>
<input
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setFirstName(e.target.value);
}}
className="bg-gray-200 p-2 rounded-md placeholder:text-sm placeholder:text-black"
placeholder="First name"
></input>
<input
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setLastName(e.target.value);
}}
className="bg-gray-200 p-2 rounded-md placeholder:text-sm placeholder:text-black"
placeholder="Last name"
></input>
</div>
</div>
</div>
</div>
<hr className="mt-16"></hr>
<div className="bottom w-3/4 mx-auto mt-16">
<div>
<h2 className="text-xl font-bold">Professional info</h2>
<p className="text-sm font-extralight">
Provide your professional info
</p>
</div>
<div className="mt-14">
<div className="flex flex-col gap-8 items-center">
<input
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setLanguages(e.target.value);
}}
className="w-3/4 bg-gray-200 p-2 rounded-md placeholder:text-sm placeholder:text-black"
placeholder="Language(s) separate with comma"
></input>
<input
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setEmploymentType(e.target.value);
}}
className="w-3/4 bg-gray-200 p-2 rounded-md placeholder:text-sm placeholder:text-black"
placeholder="Type of employment"
></input>
<input
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setProfession(e.target.value);
}}
className="w-3/4 bg-gray-200 p-2 rounded-md placeholder:text-sm placeholder:text-black"
placeholder="Profession"
></input>
<input
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setProficiency(e.target.value);
}}
className="w-3/4 bg-gray-200 p-2 rounded-md placeholder:text-sm placeholder:text-black"
placeholder="Proficiency level"
></input>
</div>
</div>
</div>
<div className="w-3/4 flex justify-end mt-7">
<button onClick={(e) => submitData(e)} className="bg-sky-800 p-2 text-white rounded-md">Submit</button>
</div>
</div>
);
};
Read other threads about similar issue but there was mentioned using async, i believe there would be another way to fix this. Thank you
Hi there setState in Reactjs is asynchronous so when you called submitData console.log(formData) gets triggered even before the setState completed its task and with asynchronous code, javascript does not have to wait for setState to finish up it just keeps running other tasks.
setFormData((prevData: anyType) => {
const newData = [...prevData, data];
console.log(newData);
return newData;
});
I have a form with two fields. One is a textfield and I cant get the data without any problem. The second field is a dropdown. This dropdown is a separate component within the form.
How can I pass the selected dropdown value with my form?
The setup is like this:
Form:
import { useState } from 'react';
import { SensorTypeDropdown } from '../add/SensorTypeDropdown'
const AddSensor = () => {
const [imei, setImei] = useState('');
const handleSubmit = (event: any) => {
alert('Sensor with IMEI: ' + imei + ' created.');
event.preventDefault(); //prevents page from refreshing
setImei('')//clears form input data
}
return (
<div className="container mx-auto">
<form className="bg-white shadow-md rounded px-8 pt-6 pb-8 mb-4" onSubmit={handleSubmit}>
<div className="mb-4">
<label className="block text-sm font-bold mb-2" htmlFor="imei">
IMEI
</label>
<input className="border rounded w-full py-2 px-3 focus:shadow-outline focus:outline-sky-700" value={imei} onChange={event => setImei(event.target.value)} id="sensorName" />
</div>
<div className="mb-6">
<label className="block text-sm font-bold mb-2" htmlFor="sensorType">
Sensor type
</label>
<SensorTypeDropdown/>
</div>
<div className="flex items-center justify-between">
<input type="submit" className="cursor-pointer bg-sky-700 hover:bg-sky-800 text-white font-bold py-2 px-4 rounded focus:shadow-outline" value="Create sensor" />
</div>
</form>
</div>
)
}
export default AddSensor;
And my separate dropdown component:
import { SharedAmbientSurrounding } from "#libs/data";
import { useState } from "react";
import { getEnumKeys } from "../../helpers/getEnumKeys";
export const SensorTypeDropdown = () => {
const [currentType, setCurrentType] = useState<SharedAmbientSurrounding>(SharedAmbientSurrounding.TEMPERATURE);
const [selectedType, setSelectedType] = useState('')
return (
<select
value={currentType}
onChange={(e) => {
setCurrentType(SharedAmbientSurrounding[e.target.value as keyof typeof SharedAmbientSurrounding]);
}}
>
{getEnumKeys(SharedAmbientSurrounding).map((key, index) => (
<option key={index} value={SharedAmbientSurrounding[key]}>
{key}
</option>
))}
</select>
);
}
Any help is appreciated. Thanks!
You should manage the dropdown state in the parent component AddSensor
AddSendor
const AddSensor = () => {
const [imei, setImei] = useState('');
const [type, setType] = useState(SharedAmbientSurrounding.TEMPERATURE);
const handleSubmit = (event) => {
alert('Sensor with IMEI: ' + imei + ' and type: ' + type + ' created.');
event.preventDefault(); //prevents page from refreshing
//clears form input data
setImei('');
setType(SharedAmbientSurrounding.TEMPERATURE);
}
....
<div className="mb-6">
<label className="block text-sm font-bold mb-2" htmlFor="sensorType">
Sensor type
</label>
<SensorTypeDropdown value={type} onChange={setType} />
</div>
...
}
SensorTypeDropdown
export const SensorTypeDropdown = ({value, onChange}) => {
return (
<select
value={value}
onChange={e => onChange(e.target.value)}
>
{getEnumKeys(SharedAmbientSurrounding).map((key, index) => (
<option key={key} value={SharedAmbientSurrounding[key]}>
{key}
</option>
))}
</select>
);
}
I want to store this react hook and password field in a separate js file and I want to import it to my register form how can I do that?
Password Field
<label class="font-medium block mb-1 mt-6 text-gray-700" for="password">
Password
</label>
<div class="relative w-full">
<div class="absolute inset-y-0 right-0 flex items-center px-2">
<input class="hidden js-password-toggle" id="toggle" type="checkbox" />
<label class="bg-gray-300 hover:bg-gray-400 rounded px-2 py-1 text-sm text-gray-600 font-mono cursor-pointer js-password-label" for="toggle">show</label>
</div>
<input class="appearance-none border-2 rounded w-full py-3 px-3 leading-tight border-gray-300 bg-gray-100 focus:outline-none focus:border-indigo-700 focus:bg-white text-gray-700 pr-16 font-mono js-password" id="password" type="password" autocomplete="off"
/>
</div>
Password function filed
useEffect(() => {
const passwordToggle = document.querySelector('.js-password-toggle')
passwordToggle.addEventListener('change', function() {
const password = document.querySelector('.js-password'),
passwordLabel = document.querySelector('.js-password-label')
if (password.type === 'password') {
password.type = 'text'
passwordLabel.innerHTML = 'hide'
} else {
password.type = 'password'
passwordLabel.innerHTML = 'show'
}
password.focus()
})
}, [])
Form That I want to add this password filed
import React, { useEffect, useState } from "react";
import Buttons from "../../../elements/form/Button";
import PasswordInput from "../../../elements/form/PasswordInput";
import { Col, Input, Row, Select, Form, InputNumber, Divider } from "antd";
import { ChangePassword, sentOtp, changePhoneNumber, requestOtp } from "./SecurityApi";
const { Option } = Select;
const Security = () => {
const [phoneNo, setPhoneNo] = useState("");
const [otp, setOtp] = useState("");
//const [password, setPassword] = useState("");
const [conPassword, setConPassword] = useState("");
const [passError, setPassError] = useState();
const [otpError, setOtpError] = useState();
const [user, setUser] = useState(null);
//changing password
const [editPasswordState, setEditPasswordState] = useState(false);
const [password, setPassword] = useState({
password: "",
});
const handlePasswordChange = async () => {
if (password === user.password) {
return;
}
if (password.trim() === "") {
console.log("cant be empty");
return;
}
await ChangePassword(password, setUser);
};
useEffect(() => {
setPassError(password && conPassword && password !== conPassword);
}, [password, conPassword]);
useEffect(() => {
setOtpError(!otp && phoneNo);
}, [otp]);
//onSubmit password
const onSubmit = async () => {
if (passError) return false;
try {
const res = await ChangePassword({ password });
setPassword("");
setConPassword("");
alert("Password Changed !!");
} catch (error) {
alert("Something went wrong !!");
}
};
//OTP
const sendOtp = async () => {
if (phoneNo === "") return false;
try {
const res = await sentOtp(phoneNo);
alert(`Otp is ${res} !!`);
} catch (error) {
alert("Something went wrong !!");
}
};
//Changing the phone number
const changePhoneNo = async () => {
if (phoneNumber === "") return false;
if (otp === "") {
setOtpError(true);
return false;
}
try {
const res = await changePhoneNumber(phoneNo);
setPhoneNumber("");
setOtp("");
alert("Phone Number Changed !!");
} catch (error) {
alert("Something went wrong !!");
}
};
const [editPhoneNumberState, setEditPhoneNumberState] = useState(false);
const [phoneNumber, setPhoneNumber] = useState({
password: "",
});
const handlePhoneNumberChange = async () => {
if (phoneNumber === user.phoneNumber) {
return;
}
if (password.trim() === "") {
console.log("cant be empty");
return;
}
await ChangePassword(password, setUser);
};
return (
<>
<div className="md:pl-8 sm:pl-0 ">
{/* ---------- Password Change ------------ */}
<div class="mt-10 sm:mt-0">
<div class="md:grid md:grid-cols-3 md:gap-6">
<div class="md:col-span-1">
<div class="px-4 sm:px-0">
<h3 class="text-lg font-medium leading-6 text-gray-900">
Change Password
</h3>
<p class="mt-1 text-sm text-gray-600">Change password here</p>
</div>
</div>
<div class="mt-5 md:mt-0 md:col-span-2">
<form action="#" method="POST">
<div class="shadow overflow-hidden sm:rounded-md">
<div class="px-4 py-3 bg-gray-50 text-right sm:px-6">
<div class="grid grid-cols-6 gap-6">
<div class="col-span-6 sm:col-span-3">
<Buttons
title={
!editPasswordState ? "Edit Full Password" : "Cancel"
}
outline
onClick={() => {
setEditPasswordState(!editPasswordState);
}}
class="inline-flex
justify-center
py-2 px-4 border
border-transparent
shadow-sm text-sm
font-medium rounded-md
text-white bg-red-400
hover:bg-red-400
focus:outline-none f
ocus:ring-2
focus:ring-offset-2
focus:ring-red-500"
/>
</div>
<div class="col-span-6 sm:col-span-3">
{editPasswordState && (
<Buttons
title="Save"
onClick={async () => {
setEditPasswordState(false);
await handlePasswordChange();
}}
/>
)}
</div>
</div>
</div>
<div class="px-4 py-5 bg-white sm:p-6">
{/* OTP Check*/}
<div class="grid grid-cols-6 gap-1">
<div class="col-span-4 sm:col-span-3">
<label
for="first-name"
class="block text-sm font-medium text-gray-700"
>
Send OTP
</label>
<Buttons
title={"Request OTP"}
outline
onClick={async () => {
await requestOtp(user.phoneNumber);
}}
/>
</div>
<div class="col-span-6 sm:col-span-3">
<label
for="last-name"
class="block text-sm font-medium text-gray-700"
>
Enter OTP
</label>
<input
type="text"
name="last-name"
id="last-name"
autocomplete="family-name"
class="mt-1
focus:ring-red-400
focus:border-red-400
block w-full
shadow-sm sm:text-sm
border-gray-300 rounded-md"
/>
{passError && (
<Col className="text-red-500">
OTP doesn't Correct !!
</Col>
)}
</div>
</div>
{/* OTP Check End */}
</div>
<div class="px-4 py-5 bg-white sm:p-6">
{/* Password Check*/}
<div class="grid grid-cols-6 gap-6">
<div class="col-span-6 sm:col-span-3">
<label
for="first-name"
class="block text-sm font-medium text-gray-700"
>
New Password
</label>
<Input.Password
value={password}
onChange={(event) => {
setPassword(event.target.value);
}}
class="mt-1
focus:ring-red-400
focus:border-red-400
block w-full
shadow-sm sm:text-sm
border-gray-300 rounded-md"/>
This is the Place
</div>
<div class="col-span-6 sm:col-span-3">
<label
for="last-name"
class="block text-sm font-medium text-gray-700"
>
Confirm New Password
</label>
<Input.Password
value={conPassword}
onChange={(event) => {
setConPassword(event.target.value);
}}
/>
{passError && (
<Col className="text-red-500">
Password doesn't match !!
</Col>
)}
</div>
</div>
{/* Password Check End*/}
</div>
</div>
</form>
</div>
</div>
</div>
{/* ------- End Password Change -------- */}
<div class="px-4 py-1 bg-black-50 text-right sm:px-6">
<Divider></Divider>
</div>
{/* ------------ Mobile Number Change ---------------- */}
<div class="mt-10 sm:mt-0">
<div class="md:grid md:grid-cols-3 md:gap-6">
<div class="md:col-span-1">
<div class="px-4 sm:px-0">
<h3 class="text-lg font-medium leading-6 text-gray-900">
Change Mobile Number
</h3>
<p class="mt-1 text-sm text-gray-600">
Use a permanent address where you can receive mail.
</p>
</div>
</div>
<div class="mt-5 md:mt-0 md:col-span-2">
<form action="#" method="POST">
<div class="shadow overflow-hidden sm:rounded-md">
<div class="px-4 py-3 bg-gray-50 text-right sm:px-6">
<div class="grid grid-cols-6 gap-6">
<div class="col-span-6 sm:col-span-3">
<Buttons
title={
!editPhoneNumberState ? "Edit Phone Number" : "Cancel"
}
outline
onClick={() => {
setEditPhoneNumberState(!editPhoneNumberState);
}}
className={" flex items-center justify-center "}
/>
</div>
<div class="col-span-6 sm:col-span-3">
{editPhoneNumberState && (
<Buttons
title="Save"
onClick={async () => {
setEditPhoneNumberState(false);
await handlePhoneNumberChange();
}}
/>
)}
</div>
</div>
</div>
<div/>
<div class="px-4 py-5 bg-white sm:p-6">
{/** Email Verification */}
<div class="grid grid-cols-6 gap-6">
<div class="col-span-6 sm:col-span-3">
<label
for="first-name"
class="block text-sm font-medium text-gray-700"
>
Email
</label>
<Buttons
title="Send Email"
onClick={sendOtp}
outline
class=" inline-flex
justify-center
py-2 px-4 border
border-transparent
shadow-sm text-sm
font-medium rounded-md
text-white bg-red-400
hover:bg-red-400
focus:outline-none f
ocus:ring-2
focus:ring-offset-2
focus:ring-red-500"
/>
</div>
<div class="col-span-6 sm:col-span-3">
<label
for="last-name"
class="block text-sm font-medium text-gray-700"
>
Verification Code
</label>
<input
type="text"
name="last-name"
id="last-name"
autocomplete="family-name"
class="mt-1
focus:ring-red-400
focus:border-red-400
block w-full
shadow-sm sm:text-sm
border-gray-300 rounded-md"
/>
</div>
</div>
{/** End Email Verification */}
{/** Phone Number change */}
<div class="grid grid-cols-6 gap-6">
<div class="col-span-6 sm:col-span-3">
<label
for="first-name"
class="block text-sm font-medium text-gray-700"
>
New Mobile Number
</label>
<input
type="text"
name="first-name"
id="first-name"
autocomplete="given-name"
class="mt-1
focus:ring-red-400
focus:border-red-400
block w-full
shadow-sm sm:text-sm
border-gray-300 rounded-md"
/>
</div>
</div>
{/** End Email change */}
</div>
</div>
</form>
</div>
</div>
</div>
{/* ------------ Mobile Number Change ---------------- */}
</div>
</>
);
};
export default Security;
Above mention between two section, the password field should be added
Ok, lets assume this is your form:
import PasswordField from "/PasswordField"
const Form = () => {
const [password, setPassword] = useState("")
const [name, setName] = useState("")
const [lastName, setLastName] = useState("")
const submitForm = () => {
//your logic for submitting form
}
return (
<form onSubmit={submitForm}>
<input type="text" value={name} onChange={(e) => setName(e.target.value)} />
<input type="text" value={lastName} onChange={(e) => setLastName(e.target.value)} />
<PasswordField password={password} setPassword={setPassword} />
</form>
);
};
export default Form;
Note we are importing the PasswordField component to be used inside the form, and passing the value and the function to change that value as props to the PasswordField component.
Now lets create the Password component:
import React, {useState} from 'react';
const PasswordField = (props) => {
return (
<div>
<label class="font-medium block mb-1 mt-6 text-gray-700" for="password">
Password
</label>
<input id={"password"} type="password" value={props.password} onChange={(e) => props.setPassword(e.target.value)} />
</div>
);
};
export default PasswordField;
Obviously, you need to add your classes and styling accordingly, but I thing this could get you going.
I have three components for form:
CreateInvoice.js (Parent component Form)
Item.js (Child Component)
Input.js (simple input with some styling)
When I try to access the register method to the Item.js file using this code const { register } = useFormContext();, it shows me this error message
TypeError: register is not a function
Input
src/components/Input.js:5
2 |
3 | const Input = ({ inputName, readOnly, register }) => {
4 | return (
> 5 | <input {...(readOnly && { disabled: true, readOnly, value: 0 })} type="text" id={inputName} className="w-full bg-primaryOne p-3 rounded-md shadow-md border border-borderOne focus:outline-none focus:border-secondaryOne transition text-white font-bold text-xs" {...register(inputName, { required: true })} />
6 | )
7 | }
8 |
But When I directly call useForm in Item.js, it doesn't show me an error message and when I add a new item to the form, it appends an empty object to the invoices array like this.
Anyone, please help me to fix this.
Input component code(Input.js):
import React from 'react'
const Input = ({ inputName, readOnly, register }) => {
return (
<input {...(readOnly && { disabled: true, readOnly, value: 0 })} type="text" id={inputName} className="w-full bg-primaryOne p-3 rounded-md shadow-md border border-borderOne focus:outline-none focus:border-secondaryOne transition text-white font-bold text-xs" {...register(inputName, { required: true })} />
)
}
export default Input
Whole form code(CreateInvoice.js):
import React, { useState } from 'react'
import { useForm, Controller, useFieldArray, FormProvider } from "react-hook-form";
import DatePicker from 'react-datepicker'
import "react-datepicker/dist/react-datepicker.css";
import Label from './Label';
import Input from './Input';
import Item from './Item';
// import { useDispatch } from 'react-redux';
// import { createInvoice } from '../actions/invoices';
const CreateInvoice = ({ openForm, setOpenForm }) => {
const { register, control, handleSubmit, errors } = useForm();
const [newItems, setNewItems] = useState([]);
const { fields, append, remove } = useFieldArray({
control,
name: "invoices"
});
// const dispatch = useDispatch();
const onSubmit = data => {
console.log(data);
// dispatch(createInvoice(data));
};
return (
<div className={`transition ${!openForm ? 'transform translate-x-full hidden' : '-translate-x-full'}`}>
<div className="fixed top-0 left-0 flex items-center justify-center w-full h-screen z-10" onClick={() => setOpenForm(!openForm)}></div>
<div className="fixed top-0 left-0 z-20 ml-24">
<FormProvider >
<form onSubmit={handleSubmit(onSubmit)} className="w-screen max-w-2xl h-screen bg-primaryTwo p-14">
<h1 className="text-white text-2xl font-bold mb-10">Create Invoice</h1>
<div className="overflow-scroll w-full h-full flex flex-col pr-7 content-area pb-10">
<small className="text-secondaryTwo font-bold text-xs">Bill Form</small>
<div>
<Label labelName="Street Address" />
<Input inputName="streetAddress" register={register} />
</div>
<div className="flex justify-between flex-wrap">
<div>
<Label labelName="City" />
<Input inputName="city" register={register} />
</div>
<div>
<Label labelName="Post Code" />
<Input inputName="postCode" register={register} />
</div>
<div>
<Label labelName="Country" />
<Input inputName="country" register={register} />
</div>
</div>
<small className="text-secondaryTwo font-bold text-xs mt-8">Bill To</small>
<div>
<Label labelName="Client Name" />
<Input inputName="clientName" register={register} />
</div>
<div>
<Label labelName="Client Email" />
<Input inputName="clientEmail" register={register} />
</div>
<div>
<Label labelName="Street Address" />
<Input inputName="clientStreetAddress" register={register} />
</div>
<div className="flex flex-wrap justify-between">
<div>
<Label labelName="City" />
<Input inputName="clientCity" register={register} />
</div>
<div>
<Label labelName="Post Code" />
<Input inputName="clientPostCode" register={register} />
</div>
<div>
<Label labelName="Country" />
<Input inputName="clientCountry" register={register} />
</div>
</div>
<div className="flex justify-between">
<div className="w-1/2 mr-2">
<Label labelName="Invoice Date" />
<Controller
control={control}
name="paymentDue"
render={({ field }) => (
<DatePicker
className="w-full bg-primaryOne p-3 rounded-md shadow-md border border-borderOne focus:outline-none focus:border-secondaryOne transition text-white font-bold text-xs"
onChange={(date) => field.onChange(date)}
selected={field.value}
/>
)}
/>
</div>
<div className="w-1/2 ml-2">
<Label labelName="Payment Terms" />
<select className="w-full bg-primaryOne p-3 rounded-md shadow-md border border-borderOne focus:outline-none focus:border-secondaryOne transition text-white font-bold text-xs" name="Payments Term" id="Payments Term" {...register("Payments Term", { required: true })}>
<option value="1">Next 1 Day</option>
<option value="7">Next 7 Days</option>
<option value="14">Next 14 Days</option>
<option value="30">Next 30 Days</option>
</select>
</div>
</div>
<div>
<Label labelName="Descriptions" />
<Input inputName="descriptions" register={register} />
</div>
<p className="text-gray-500 text-lg mt-6 mb-2 font-bold">Item List</p>
<div>
// in this code I need help
{fields.map((invoice, index) => <Item key={invoice.id} index={index} remove={remove} />)}
</div>
<button className="w-full bg-borderOne hover:bg-primaryOne transition text-white border-none rounded-full mt-4 p-4 text-xs font-bold flex justify-center" onClick=
{e => {
e.preventDefault();
append({});
}}
>
<span className="font-semibold mr-1">+</span>Add New Item
</button>
</div>
<div className="flex justify-between py-4">jsx
<button className="rounded-full text-neutral text-xs bg-primaryOne outline-none px-8 py-4 font-bold" onClick={() => setOpenForm(!openForm)}>Discard</button>
<div className="pr-7">
<button className="rounded-full text-neutral text-xs bg-primaryOne outline-none px-8 py-4 font-bold">Save as Draft</button>
<input className="rounded-full text-neutral text-xs bg-secondaryTwo outline-none ml-2 px-8 py-4 font-bold" type="submit" value="Save & Send" />
</div>
</div>
</form>
</FormProvider>
</div >
</div >
)
}
export default CreateInvoice
whole child component(Item.js) code
import React, { useState } from 'react'
import Input from './Input'
import Label from './Label'
import { useFormContext } from "react-hook-form";
const Item = ({ index, remove }) => {
const { register } = useFormContext();
return (
<div className="flex justify-center items-end">
<div className="w-3/5">
<Label labelName="Item Name" />
<Input inputName="inputName" register={register} />
</div>
<div className="w-2/12 mx-3">
<Label labelName="Qty." />
<Input inputName="quantity" register={register} />
</div>
<div className="w-1/3">
<Label labelName="Price" />
<Input inputName="price" register={register} />
</div>
<div className="mx-3">
<Label labelName="Total" />
<Input inputName="total" register={register} readOnly />
</div>
<button className="mb-4" aria-label="delete button" onClick={
e => {
e.preventDefault();
remove(index);
}}
>
<svg className="transition fill-current text-gray-400 hover:fill-current hover:text-red-400" width="13" height="16" xmlns="http://www.w3.org/2000/svg"><path d="M11.583 3.556v10.666c0 .982-.795 1.778-1.777 1.778H2.694a1.777 1.777 0 01-1.777-1.778V3.556h10.666zM8.473 0l.888.889h3.111v1.778H.028V.889h3.11L4.029 0h4.444z" fillRule="nonzero" /></svg></button>
</div>
)
}
export default Item
You're missing to spread the formMethods to the <FormProvider /> in your <CreateInvoice /> component.
const CreateInvoice = ({ openForm, setOpenForm }) => {
const formMethods = useForm();
const { register, control, handleSubmit, formState: { errors } } = formMethods;
return (
<FormProvider {...formMethods} >
...
</FormProvider>
);
}
For the second issue:
you're not registering the field array item <input /> components correctly and miss to set the index, so RHF can't setup the link to this fields. Check the demo, i just passed the fieldId as a prop to your <Item /> component.
since v7 RHF's errors object is a property of the formState property returned by useForm. I updated the code example above.
you should set the defaultValues for your field array item when you're calling append. From the docs:
When you append, prepend, insert and update the field array, the obj can't be empty object rather need to supply all your input's defaultValues.