DefaultValues of react-hook-form is not setting the values to the Input fields in React JS - reactjs

I want to provide default values in the input field using react-hook-form. First I retrieve the user data from the API endpoint and then setting the state users to that user data. Then I pass the state users to the defaultValues of useForm().
import React, { useState, useEffect } from "react";
import { useForm } from "react-hook-form";
import axios from "axios";
function LoginFile() {
const [users, setUsers] = useState(null);
useEffect(() => {
axios
.get("http://localhost:4000/users/1")
.then((res) => setUsers(res.data));
}, []);
useEffect(() => {
console.log(users);
}, [users]);
const { register, handleSubmit, errors } = useForm({
defaultValues: users,
});
return (
<div>
<form onSubmit={handleSubmit(onSubmit)}>
Email <input type="email" name="email" ref={register} /><br />
firstname <input name="firstname" ref={register} /><br/>
<input type="submit" />
</form>
</div>
);
}
export default LoginFile;
I did by the above code but didn't work as expected. All the input fields are still empty. I want to have some default values in the input field of my form.

The problem is that during the first render, users is the useState hook's initial value, which is null. The value only changes after the axios.get() request finishes, which is after the initial render. This means that the the default values passed to useForm is null.
The documentation for defaultValues says the following:
defaultValues are cached on the first render within the custom hook. If you want to reset the defaultValues, you should use the reset api.
So, you'll just need to use reset to reset the form manually to the values which you fetch. The documentation for reset says the following:
You will need to pass defaultValues to useForm in order to reset the Controller components' value.
However, it's unclear from the documentation whether null is enough as the defaultValues, or if you need to pass it a proper object with fields for each input. To play it safe, let's assume it's the latter.
The code for doing this would look something like this:
function LoginFile() {
const [users, setUsers] = useState({ email: "", firstname: "" });
const { register, handleSubmit, errors, reset } = useForm({
defaultValues: users,
});
useEffect(() => {
axios.get("http://localhost:4000/users/1").then((res) => {
setUsers(res.data);
reset(res.data);
});
}, [reset]);
useEffect(() => {
console.log(users);
}, [users]);
return (
<div>
<form onSubmit={handleSubmit(onSubmit)}>
Email <input type="email" name="email" ref={register} />
<br />
firstname <input name="firstname" ref={register} />
<br />
<input type="submit" />
</form>
</div>
);
}
Additionally, if the only reason for the useState hook is to store the value for defaultValues, you don't need it at all and can clean up the code to be:
function LoginFile() {
const { register, handleSubmit, errors, reset } = useForm({
defaultValues: { email: "", firstname: "" },
});
useEffect(() => {
axios.get("http://localhost:4000/users/1").then((res) => {
reset(res.data);
});
}, [reset]);
return (
<div>
<form onSubmit={handleSubmit(onSubmit)}>
Email <input type="email" name="email" ref={register} />
<br />
firstname <input name="firstname" ref={register} />
<br />
<input type="submit" />
</form>
</div>
);
}

That definitely Worked. Using the reset API and the UseEffect.
Starting with empty stings as default values and the updating them as effects with the reset. Here is my code. Was using TypeScript with Ionic here as well...
const { control: editControl, handleSubmit: editSubmit, reset } = useForm<EditFormType>({
defaultValues: {
question: "",
optionA: "",
optionB: "",
optionC: "",
optionD: "",
}
});
useEffect(() => {
let defaults ={
question: editQuestion?.body,
optionA: editQuestion?.options[0].body,
optionB: editQuestion?.options[1].body,
optionC: editQuestion?.options[2].body,
optionD: editQuestion?.options[3].body,
}
reset(defaults)
}, [editQuestion, reset])

Related

Is there any way of creating a component with images based on user input?

I am trying to let the user input a image that i later can display in another component, It is supposed to be displayed inside my "Card" component which is displayed inside my "Grid" component. The grid component then maps and displays all the houses in the "houses" array which also is a separate component. Manually creating houses inside the array works perfectly but when the user is trying to add a house everything but the images works. The console log in submitListing will show that the houses beeing added does contain an image but it is not beeing displayed in the grid. (Also, the first house that is submitted only contains an empty array for mainImg but the rest contains the "blob" image) Entire project: https://codesandbox.io/s/relaxed-orla-fqcwoy?file=/src/components/header/AddListing.jsx
HTML
<textarea
value={listing.area}
name="area"
onChange={handleChange}
className="area"
></textarea>
<input
onChange={handleInputChange}
name="mainImg"
type={"file"}
></input>
JS
const [listing, setListing] = useState({
area: "",
mainImg: [],
});
function handleChange(e) {
const { name, value } = e.target;
setListing((previousListing) => {
return { ...previousListing, [name]: value };
});
}
const [file, setFile] = useState();
function handleInputChange(e) {
setFile(URL.createObjectURL(e.target.files[0]));
}
function submitListing(e) {
e.preventDefault();
setListing({
area: "",
mainImg: file,
});
houses.push(listing);
console.log(houses);
}
So, here are the issues. I might forget something but i forked codesandbox, so you will be able to compare.
In your houses.js and AddListing.jsx you had mainImage and mainImg. You need to have a strict structure and naming, they had to match.
const [file, setFile] was.. useless, you had to save blobs into the listing object itself in the mainImage, img2, etc.
About the structure again. You had your mainImg, img2, ... set to an empty array, []. That have no sense due to you dont use <input type="file" multiple>. Switched them to undefined / string.
You cannot really bind value to the input type=file in react. Just remove the value from inputs.
Be careful with useEffects and useState's setValues, without understanding how they work it is really easy to make a mistake.
Here is your updated and working AddListing.jsx
import React, { useState, useCallback, useEffect } from "react";
import "./AddListing.css";
import houses from "../../houses";
const getDefaultListing = () => {
return {
id: houses.length + 1,
area: "",
state: "",
shortDesc: "",
longDesc: "",
pricePerNight: "",
cleaningFee: "",
serviceFee: "",
mainImage: undefined,
img2: undefined,
img3: undefined,
img4: undefined,
img5: undefined
};
};
function AddListing() {
const [listing, setListing] = useState(() => getDefaultListing());
const handleChange = useCallback((e) => {
const { name, value } = e.target;
setListing((prev) => ({ ...prev, [name]: value }));
}, []);
const handleInputChange = useCallback((e) => {
const { name, files } = e.target;
const [singleImage] = files;
if (!singleImage) {
setListing((prev) => ({ ...prev, [name]: undefined }));
} else {
const blob = URL.createObjectURL(singleImage);
setListing((prev) => ({ ...prev, [name]: blob }));
}
}, []);
const submitListing = useCallback(
(e) => {
e.preventDefault();
houses.push(listing);
setListing(() => getDefaultListing());
console.log(houses);
},
[listing]
);
useEffect(() => {
console.log(listing);
}, [listing]);
return (
<div className="add-listing-div">
<form>
<p id="p1">State</p>
<p id="p2">Area</p>
<textarea
value={listing.state}
name="state"
onChange={handleChange}
className="state"
/>
<textarea
value={listing.area}
name="area"
onChange={handleChange}
className="area"
/>
<p id="p3">Short Description</p>
<textarea
value={listing.shortDesc}
name="shortDesc"
onChange={handleChange}
className="short-description"
/>
<p id="p4">Long Description</p>
<textarea
value={listing.longDesc}
name="longDesc"
onChange={handleChange}
className="long-description"
/>
<p id="p5">Price/Night</p>
<p id="p6">Cleaning Fee</p>
<p id="p7">Service Fee</p>
<textarea
value={listing.pricePerNight}
name="pricePerNight"
onChange={handleChange}
className="price-per-night"
/>
<textarea
value={listing.cleaningFee}
name="cleaningFee"
onChange={handleChange}
className="cleaning-fee"
/>
<textarea
value={listing.serviceFee}
name="serviceFee"
onChange={handleChange}
className="service-fee"
/>
<label>Main Image: </label>
<input onChange={handleInputChange} name="mainImage" type="file" />
<label>Second Image: </label>
<input onChange={handleInputChange} name="img2" type="file" />
<label>Third Image: </label>
<input onChange={handleInputChange} name="img3" type="file" />
<label>Fourth Image: </label>
<input onChange={handleInputChange} name="img4" type="file" />
<label>Fifth Image: </label>
<input onChange={handleInputChange} name="img5" type="file" />
<button onClick={submitListing}>Submit</button>
</form>
</div>
);
}
export default AddListing;

React-Hook-Form - adding icon inside input when form validation goes wrong

I am trying to create form with react and already took care of most of the validations (like displaying the message). I would also like to add icon/img inside the input when it is invalid. What would be the best approach to this?
Also, how to reset inputs after submitting form?
import React, { useEffect } from 'react';
import { useForm } from 'react-hook-form';
function Form() {
const { register, handleSubmit, formState: { errors } } = useForm();
const onSubmit = (data) => {
console.log(data)
}
const registerOptions = {
email: {
required: "Email cannot be empty",
pattern: {
value: /^[\w-\.]+#([\w-]+\.)+[\w-]{2,4}$/,
message: "Looks like this is not an email"
}
}
}
return (
<div className="form">
<form onSubmit={handleSubmit(onSubmit)}>
<input
type="text"
placeholder='Email Address'
name="email"
{...register('email', (registerOptions.email)
)} />
{errors.email && <p>{errors.email.message}</p>}
<input type="submit" value="Claim your free trial" className="submit" />
</form>
</div>
)
}
export default Form;
To reset the Input fields you could do this:
const onSubmit = (data, e) => {
e.target.reset();
console.log(data);
}
If you want to add icons inside your inputs, I recommend you to search for bootstrap and https://www.npmjs.com/package/classnames

Saving object in React.useState

I have a form in my react project, and I want the value of each field to be stored in the state.
instead of having multiple states for each field, how can I store the form value as an object in the state? and more importantly how can I access it? (with react hooks)
import React from 'react';
export const UserData = () => {
return(
<form>
<input type="text" placeholder="Name" />
<input type="email" placeholder="Email" />
<button>Confirm</button>
</form>
)
}
React Hooks allows you to define a JavaScript Object using useState. See
https://daveceddia.com/usestate-hook-examples/
function LoginForm() {
const [form, setState] = useState({
username: '',
password: ''
});
Update the form using the function :
const updateField = e => {
setState({
...form,
[e.target.name]: e.target.value
});
};
Call the function onSubmit of the button
<form onSubmit={updateField}>
<input type="text" placeholder="Name" />
<input type="email" placeholder="Email" />
<Button >Confirm</button>
</form>
You can use useState hook. Check this
const [state, setState] = useState({ name, email });
To set the state similar to setState in class based component:
setState({name: 'react', email: 'react'})
To access the state value:
import React, { useState } from 'react';
export const UserData = () => {
const [state, setState] = useState({ name, email });
return(
<form>
<input type="text" placeholder="Name" value={state.name} />
<input type="email" placeholder="Email" value={state.email} />
<button>Confirm</button>
</form>
)
}
You should create an initial state value if you want to store the form values as an object using useState, so you can rollback to the initial state after an error. Example,
const initialState = {
name: "",
email: ""
};
export const UserData = () => {
const [formState, setFormState] = useState(initialState);
const submitHandler = event => {
event.preventDefault();
console.log(formState);
};
return (
<form onSubmit={submitHandler}>
<input
type="text"
placeholder="Name"
value={formState.name}
onChange={e => {
setFormState({ ...formState, name: e.target.value });
}}
/>
<input
type="email"
placeholder="Email"
value={formState.email}
onChange={e => {
setFormState({ ...formState, email: e.target.value });
}}
/>
<button>Confirm</button>
</form>
);
};
Working demo in codesandbox.

My Login page in React isn't giving the output that I want

I am new to React and Javascript and one of the exercises that I was doing is to create a Login Page. When I give the username and password I want them to be displayed in the console the following way:
{username: "username", password: "password"}
Instead I am getting the following output:
{p:"p", pa:"pa", pas:"pas", pass:"pass",..., password:"password" , u:"u", us:"us", use:"use"..., username:"username" }
I am following the tutorials but I am getting that result. Can somebody help me spotting my error?
import React, {useState} from 'react';
import {Button, TextField} from '#material-ui/core';
export default function Login(){
const [formState, setFormState] = useState({
values:{}
})
};
const handleChange = event => {
event.persist();
setFormState( formState => ({
...formState,
values:{
...formState.values,
[event.target.value]:event.target.value
}
}));
};
const handleSubmit = event =>{
event.preventDefault();
console.log(formState.values)
}
return (
<div>
<form className="form" onSubmit={handleSubmit}>
<TextField
label="Username"
name="username"
onChange={handleChange}
value=formState.values.username
></TextField>
<TextField
label="Password"
name="password"
onChange={handleChange}
value=formState.values.password
></TextField>
<Button type="submit">Login</Button>
</form>
</div>
);
Your handle change is using event.target.value as key. You need to use the input's name which identifies your input box instead.
Change your handleChange like this -
const handleChange = event => {
event.persist();
const values = { values: { ...formState.values, [event.target.name]:event.target.value}}; //name as key instead of value
setFormState(values);
};
What I would recommend is structuring your state like this - {username: "", password: ""};

React-hook-form input fields match validation best practice

What's the best practice when doing input fields match validation when dealing with React-hook-form? For example, when matching email inputs, etc.
While looking into email match validation with React-hook-form found an issue while trying to separate error messages from "coupled elements" through their validation method. The ref only takes one argument that is used for React-hook-form register, while needing to use useRef to access the current.value for value matching, as follows:
import React, { useRef } from "react";
import ReactDOM from "react-dom";
import { useForm } from "react-hook-form";
function App() {
const { register, handleSubmit, errors } = useForm();
const inputEmail = useRef(null)
const onSubmit = data => {
console.log('onSubmit: ', JSON.stringify(data))
}
return (
<form onSubmit={handleSubmit(onSubmit)}>
<label htmlFor="email">Email</label>
<input
name="email"
type="email"
ref={inputEmail}
/>
{/* desired: show `email` error message */}
<label htmlFor="email">Email confirmation</label>
<input
name="emailConfirmation"
type="email"
ref={register({
validate: {
emailEqual: value => (value === inputEmail.current.value) || 'Email confirmation error!',
}
})}
/>
{errors.emailConfirmation && <p>{errors.emailConfirmation.message}</p>}
<input type="submit" />
</form>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
While this pattern seems to be an option when doing input field matching it does not play well with React-hook-form!
For example, the error message is coupled with one input case only and has no separate messages for each independent field, or one of the input fields does not have the register assigned to it, this means that the property required is not set, etc.
So, I'm looking into a good practice or pattern that solves:
Keeping error messages separated by the input field
The validation method, when testing the match should be able to reference the twin field value in a React compliant way and not
through the DOM (document.querySelector, etc)
You shouldn't need the manual ref for inputEmail. Instead, use the getValues method to fetch the current value of your whole form.
const { register, getValues } = useForm()
Then you register both inputs and call getValues from your custom validation.
<input
name="email"
type="email"
ref={register}
/>
<input
name="emailConfirmation"
type="email"
ref={register({
validate: {
emailEqual: value => (value === getValues().email) || 'Email confirmation error!',
}
})}
/>
For this you could use Yup library, which is great:
Add validationSchema to your config object when instantiating useForm and pass a valid Yup schema. Like so:
const Schema = yup.object().shape({
email: yup.string().required('Required field'),
emailConfirmation: yup
.string()
.oneOf([yup.ref('email')], 'Emails must match')
.required('Required field'),
});
// How to add it to your useForm
const { register } = useForm({
validationSchema: Schema
})
Your component should look something like this:
function App() {
const { register, handleSubmit, errors } = useForm({
validationSchema: Schema
});
const onSubmit = data => {
console.log('onSubmit: ', JSON.stringify(data))
}
return (
<form onSubmit={handleSubmit(onSubmit)}>
<label htmlFor="email">Email</label>
<input
name="email"
type="email"
ref={register}
/>
{/* desired: show `email` error message */}
<label htmlFor="email">Email confirmation</label>
<input
name="emailConfirmation"
type="email"
ref={register}
/>
{errors.emailConfirmation && <p>{errors.emailConfirmation.message}</p>}
<input type="submit" />
</form>
);
}

Resources