Can't get useRef() to work with React Hook Form - reactjs

But it does work when I disable React Hook Form and vice versa but not both together. I just want to focus on the title input field on first render. I minified it a little and imports/exports are not included in the snippet because it's usual boilerplate.
When I comment out ref={refFocus} RHF works but when I take it in again it won't function. I don't know if this is a conflict with the DOM reference since RHF is working a lot with it too. Might be a version mismatch, typescript error or something's deprecated or incompatible - I have no clue. I'd appreciate any idea.
I already browsed through the questions and found a similar issue but it lacks contents/code example and there's no answer: Why onchange, onblur and useRef doesn't work with react hook form?
And this one is not very alike, I tried that: How to set focus when using React hook form Controller component
For context, It's a simple UI where I can add tiles by filling out a form that's inside a header:
const Header = () : JSX.Element => {
const
{setCtxAdded} = useContext(TileListCtx),
addSchema = z.object({
image: z.string().url({ message: 'Value must contain a valid URL address.' }),
title: z.string(),
text: z.string().optional()
}),
refFocus = useRef<HTMLInputElement | null>(null)
useEffect(()=> refFocus.current?.focus(), [])
type AddFormValidation = z.infer<typeof addSchema>
const
{register, handleSubmit, formState:{errors}} = useForm<AddFormValidation>({
resolver: zodResolver(addSchema)
}),
addTile = async (data: Array<string> | unknown) : Promise<void> => {
const add: Response = await fetch('http://localhost:3000/api/add', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
})
const res: Array<JSON> | any = await add.json()
setCtxAdded(add.ok)
if (res.error.code == 'P2002') console.log('Title already exists.')
}
return (
<header>
<h1>X-5</h1>
<form onSubmit={handleSubmit(addTile)}>
<input {...register("image")} type='text' placeholder='Image' />
<input {...register("title")} ref={refFocus} type='text' placeholder='Title' />
<input {...register("text")} type='text' placeholder='Text' />
<button>Add Tile</button>
</form>
</header>
)
}

React hook form uses refs, but you replaced his ref with your ref.
RHF has setFocus method. Use it instead of your own ref:
const {
register,
handleSubmit,
formState: { errors },
setFocus
} = useForm<AddFormValidation>(),
useEffect(() => {
setFocus("title");
}, [setFocus]);
<form onSubmit={handleSubmit(addTile)}>
<input {...register("image")} type="text" placeholder="Image" />
<input {...register("title")} type="text" placeholder="Title" />
<input {...register("text")} type="text" placeholder="Text" />
<button>Add Tile</button>
</form>
https://codesandbox.io/s/loving-booth-2yg5ve?file=/src/Header.tsx

Related

How to bind an input's value to a link in react

I want to create a simple application where you can search for images. The application is written in React using fetch (I can't use axios) and Unsplash API. My current attempt renders a list of images with a static value "cars" into the link as shown: https://api.unsplash.com/search/photos?query=**cars**
In the code example below I am using a variable "${query}" to be able to search for images but it toes not work. I need help to figure out how to fix that. Thanks in advance!
code:
import React from "react";
import { useState, useEffect } from "react";
export default function App() {
const [data, setData] = useState(null);
const [error, setError] = useState(null);
const [query, setQuery] = useState("");
useEffect(() => {
fetch(`https://api.unsplash.com/search/photos?query=${query}`, {
headers: {
Authorization: "Client-ID UnsplashId",
},
})
.then((response) => {
if (!response.ok) {
throw new Error(
`This is an HTTP error: The status is ${response.status}`
);
}
return response.json();
})
.then((actualData) => {
console.log(actualData);
setData(actualData.results);
setError(null);
})
.catch((err) => {
setError(err.message);
setData(null);
});
}, []);
return (
<div>
{/* onSubmit={this.handleSubmit} */}
<form>
<label>
<input
placeholder="Search"
type="text"
// value={this.state.value}
// value="cars"
onChange={(e) => setQuery(e.target.value)}
/>
</label>
<input type="submit" value="Submit" />
</form>
{data &&
data.map(({ id, description, urls }) => (
<img key={id} alt={description} src={urls.regular} />
))}
</div>
);
}
I think you want to achieve conditionally firing an effect
Example
useEffect(() => {
// This will execute whenever 'query' variable changes.
}, [ query ]);
// You can bind the state using the 'value' attribute.
<input
placeholder="Search"
type="text"
value={query}
onChange={(e) => setQuery(e.target.value)}
/>
I did not quietly get the question but I think you want to do the search every time the input is changed, hence I recommend using an input instead of the form and adding "query" to the "useEffect" conditions:
useEffect(() => {
fetch(`https://api.unsplash.com/search/photos?query=${query}`, {
headers: {
Authorization: "Client-ID UnsplashId",
},
})
.then((response) => {
// here
}, [query]);
<input
placeholder="Search"
type="text"
onChange={(e) => setQuery(e.target.value)} />

Why are the useState values not defined?

I have a react application. I recently added a POST feature. When I connected it to the front end via useContext, I got the below error message:
src/components/messages/SendMessage.js
Line 57:16: 'senderName' is not defined no-undef
Line 66:16: 'senderEmail' is not defined no-undef
Line 75:16: 'senderCompanyName' is not defined no-undef
Line 84:16: 'message' is not defined no-undef
Search for the keywords to learn more about each error.
The undefined elements are the default states that I connected to the component. It is supposed to get the input data and then post to the database via Fetch. I can't figure out what happened. Before the logic was in a separate State file and I used to get the error message that the Context file is undefined, so I moved all the logic code to the component file and now the error message got more specific and says that these specific elements are undefined. But how?
Below is the code:
import React, { useContext, useState } from "react";
import companyContext from "../../logic/companies/companyContext";
const SendMessage = ({ company }) => {
const myCompanyContext = useContext(companyContext);
const { convertToLink } = myCompanyContext;
// Set defaults
// add a message
const [newMessage, setNewMessage] = useState({
date: new Date(),
recieverID: company.id,
senderName: "",
senderEmail: "",
senderCompanyName: "",
message: ``,
});
// add message function
// CRUD functions
// add a message
const addMessage = async input => {
const requestOptions = {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(input),
};
const response = await fetch(
"http://localhost:5002/messages",
requestOptions
);
const data = await response.json();
setNewMessage(data);
};
// Event actions
// add message
const onChange = change =>
setNewMessage({ ...newMessage, [change.target.name]: change.target.value });
const onAddButton = submit => {
submit.preventDefault();
addMessage(newMessage);
};
return (
<form
className="center modal"
id={"message-#" + company.id + "-" + convertToLink(company.name)}
onSubmit={onAddButton}
>
<h4>Send message to {company.name}</h4>
<input
className="input"
type="text"
name="senderName"
value={senderName}
onChange={onChange}
placeholder="Your name"
required
/>
<input
className="input"
type="text"
name="senderEmail"
value={senderEmail}
onChange={onChange}
placeholder="Your email"
required
/>
<input
className="input"
type="text"
name="senderCompanyName"
value={senderCompanyName}
onChange={onChange}
placeholder="Your company's name (optional)"
/>
<textarea
className="input"
rows="10"
type="text"
name="message"
value={message}
onChange={onChange}
placeholder="Write your message here..."
required
></textarea>
<br />
<button className="button-cta" type="submit">
Send message
</button>
<a className="delete button" href="/#!">
close
</a>
</form>
);
};
export default SendMessage;
As you can see, the "undefined" elements are connected to the component via "onChange" but it does not seem to work.
Can you try the below instead of {senderName} try {newMessage.senderName}?

preventDefault() not working on submit in React

I'm currently working in react and have a couple of forms where the onSubmit functions automatically refresh the page even if I use preventDefault(). Im passing the event into the functions as well. Could really use some guidance on why these two forms are having this issue. It hasn't been a problem elsewhere.
Here's the form. Verify is automatically passing e.
<form onSubmit={verify} className='username-password-form'>
<div className='old-password-container'>
<label className='old-password-label'>Current Password:</label>
<input
className='old-password-input'
type='password'
id={`${id}-old-password`}
value={currentPassword}
name='currentPassword'
disabled={disabled}
onChange={(e) => setCurrentPassword(e.target.value)}
/>
<Button className='submit-old' type='submit'>
Submit
</Button>
</div>
</form>
Here's the verify function called onSubmit
const verify = async (e) => {
e.preventDefault();
const user = {
username: user_name,
password: currentPassword,
};
await fetch('http://localhost:3000/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(user),
});
setDisabled(true);
};

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

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])

Formik onSubmit - remove form and success message

This is the first time I'm using Formik and I'm facing the following issues:
I created this form using a typescript starter provided in the Formik documentation, and it works, but I'd like to show a success message and remove the form once axios returns with status 200.
So,
1. how do I target the form reference inside of the axios call? normally that is as simple as e.target but the event seems not to be available in Formik.
2. how do I access the state of the form in Formik? to toggle the success message.
The full code is available here: https://codesandbox.io/s/throbbing-water-ffl2w
Thanks a lot in advance.
<Formik
initialValues={{
firstName: "",
lastName: "",
email: ""
}}
// initialStatus={{ // resetForm(); resets this
// sent: "nope"
// }}
onSubmit={(
values: Values,
{ setStatus, setSubmitting, resetForm }: FormikActions<Values>
) => {
axios({
method: "post",
url: "https://getform.io/f/15faef97-5703-4799-930d-c3e698c99967",
data: { email: values.email, values }
}).then(r => {
setSubmitting(false);
setStatus("sent");
//resetForm();
console.log("Thanks!");
});
}}
render={() => (
<Form>
<label htmlFor="firstName">First Name</label>
<Field
id="firstName"
name="firstName"
placeholder="John"
type="text"
/>
<label htmlFor="lastName">Last Name</label>
<Field id="lastName" name="lastName" placeholder="Doe" type="text" />
<label htmlFor="email">Email</label>
<Field
id="email"
name="email"
placeholder="john#acme.com"
type="email"
/>
<button type="submit" style={{ display: "block" }}>
Submit
</button>
</Form>
)}
/>
What I recommend is using state to control what to show in your component (for some reason I cannot save the codesandbox):
const BasicForm: React.FC<{}> = () => {
const [isSent, setIsSent] = React.useState(false);
then, the fetch callback:
.then(r =>
...
setIsSent(true);
Finally in your render function
render={({ isSubmitting, status }) =>
!isSent ?
<Form> ... </Form>:
<div>Success</div>
render is a function that gets props. I see that you use setStatus, so you can get status from props and make changes in Form Component
This is an outdated version of Formik v1.1.2 and I wouldn't recommend to use it as there are some Breaking Changes such as the render method has been deprecated and will be removed in future versions. You may want to use the current version which is v2.1.4
how do I target the form reference inside of the axios call?
Formik passes the values object along with other methods (called FormikBag) inside the onSubmit prop. You can pass these value directly to axios without the need to have your own onSubmit or onChange methods. Please note that <Formik> component has other props. that will give you pretty much full control/access for your needs. That said, I'd recommend to only use Formik state/methods to avoid any side effects or bugs of having the multiple states or handlers.
v2 General Syntax:
<Formik
initialValues={initialValues}
// Other Formik props...
onSubmit = {(Object: form values, Object: Formik Bag ) => {
// access form values...
}}
>
// Access render methods and props (children props)
{(props) => {
return (
<Form>
<Field> ...
</Form>
)
}
}
</Formik>
axios Example:
<Formik
initialValues={initialValues}
onSubmit={(values) => {
console.log(values) // Object holds your form values
axios({
method: "post",
url: "url",
data: { values }
})
})
/>
how do I access the state of the form in Formik? to toggle the success message.
You can use Formik setStatus method from FormikBag inside your onSubmit to pass your server response status, then you can access that status via children props Here is an example:
<Formik
initialValues={initialValues}
onSubmit={(values, setStatus) => {
axios({
method: "post",
url: "url",
data: { values }
})
.then(res => {
if (res.status === 200) {
// 200 means POST method response with success
// Pass your server response to Formik
setStatus({
sent: true,
msg: "Message has been sent! Thanks!"
// Pass more if you need
})
}
})
.catch(err => {
// Something went wrong
setStatus({
sent: false,
msg: `Error! ${err}. Please try again later.`
})
})
})
>
// Later in your code destructuring the children props and use it like so:
{({ status }) => (
<Form>
<Field ... />
{status && status.msg && (
<p className={`alert ${ status.sent ? "alert-success" : "alert-error"}`}>
{status.msg}
</p>
)}
<button>Submit</button>
</Form>
)}
</Formik>
I did fork your codesanbox and updated the dependencies versions/syntax in this codeSandbox Example. Please note that I'm no typescript expert.

Resources