Accessing error from react-hook-form using reactstrap - javascript

I created a form with reactstrap and react-hook-form. Why are my errors not displaying?
Dummy section which renders text input:
function DummySection() {
const { control } = useForm();
return (
<div>
<Controller
name="dummyName"
control={control}
rules={{ required: true }}
defaultValue=""
render={({ field: { onChange, ref }, fieldState: { error } }) => (
<TextInput onChange={onChange} innerRef={ref} error={error} />
)}
/>
</div>
);
}
Text input with error:
function TextInput({ onChange, value, innerRef, error }) {
const updateText = (e) => {
onChange(e);
// do something else below
};
return (
<div>
<Input
name="dummyName"
type="text"
onChange={(e) => updateText(e)}
value={value}
innerRef={innerRef}
/>
{error && "Cannot be blank"}
</div>
);
}
Submit Button
function SubmitBtn() {
const { handleSubmit } = useForm();
const onSubmit = (data) => console.log(data);
return <Button onClick={() => handleSubmit(onSubmit)()}>Submit</Button>;
}
Thanks.
Code Sandbox

#jamfie, you are using different useForm for each component.
Your form need to have the same instance, you can do that by using useformcontext or by passing props to components.
Also, you do not need Controller for this thing,
here is codesandbox shows how you can use reactstrap with react-hook-form.
You can also follow this answer in github issue.

Related

Formik field values arn't being passed from React Context

I have a Formik form that is using a progressive stepper and have multiple fields across different components, thus requiring the need to store the values in React Context. However none of the field values are being passed, so when I click submit, all values are empty strings and the validation fails. You can see on each Formik Field i am setting the value as {state.[field]}, which comes from the Context, so I believe something is going wrong here. Can anyone see what I'm doing wrong?
Thanks a lot
Here is my parent component
const AddSongPage = () => {
const { state, dispatch } = useUploadFormContext();
const initialValues = {
name: "",
};
const { mutate: handleCreateTrack } = useCreateSyncTrackMutation(
gqlClient,
{}
);
const handleSubmit = (values: any) => {
handleCreateTrack(
{
...values,
},
{
onSuccess() {
console.log("Track added succesfully");
},
}
);
};
const validate = Yup.object({
name: Yup.string().required("Song name is required"),
description: Yup.string().optional(),
});
return (
<Layout headerBg="brand.blue">
<Formik
onSubmit={(values) => handleSubmit(values)}
initialValues={initialValues}
validationSchema={validate}
>
<Form>
<Box> {state.activeStep === 1 && <Step1 />}</Box>
<Box> {state.activeStep === 2 && <Step2 />}</Box>
<Box> {state.activeStep === 3 && <Step3 />}</Box>
<Button type={"submit"}>Submit</Button>
</Form>
</Formik>
</Layout>
);
};
Here is step 1
const Step1 = () => {
const { state, dispatch } = useUploadFormContext();
const onInputChange = (e: FormEvent<HTMLInputElement>) => {
const inputName = e.currentTarget.name;
dispatch({
type: "SET_UPLOAD_FORM",
payload: {
[inputName]: e.currentTarget.value,
},
});
};
return (
<Stack spacing={4}>
<Field name={"name"}>
{({ field, form }: any) => (
<FormControl isInvalid={form.errors.name && form.touched.name}>
<Input
{...field}
onChange={onInputChange}
value={state.name}
/>
</FormControl>
)}
</Field>
<Field name={"description"}>
{({ field, form }: any) => (
<FormControl isInvalid={form.errors.name && form.touched.name}>
<Input
{...field}
onChange={onInputChange}
value={state.description}
/>
</FormControl>
)}
</Field>
</Stack>
);
};
export default Step1;

How to make react-hook-form require 1 checkbox?

I have a Checkbox component which works fine, until I had a Yup schema:
const Checkbox = React.forwardRef(
(
{
label,
name,
value,
onChange,
defaultChecked,
errors,
onBlur,
...rest
}: any,
forwardedRef: any
) => {
return (
<div className="grow w-full">
<label htmlFor={name}>{label}</label>
<input
type="checkbox"
value={value}
name={name}
onChange={onChange}
onBlur={onBlur}
ref={forwardedRef}
{...rest}
/>
{errors && <p className="error">{errors.message}</p>}
</div>
);
}
);
Which I use like this:
export default function App() {
const {
handleSubmit,
register,
formState: { errors }
} = useForm({
resolver: yupResolver(schema)
});
const onSubmit = (data: any) => {
console.log("clicked");
console.log(errors, "errors");
alert(JSON.stringify(data));
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<Checkbox
{...register("name", { required: "This is required." })}
name={"name"}
value={"test"}
label={"Test"}
errors={errors.name}
/>
<Checkbox
{...register("name", { required: "This is required." })}
name={"name"}
value={"test"}
label={"Test"}
errors={errors.name}
/>
<button type="submit">Submit</button>
</form>
);
}
When I go to submit my form, I get the following error:
name must be a `array` type, but the final value was: `null` (cast from the value `false`). If "null" is intended as an empty value be sure to mark the schema as `.nullable()`
Here's my sandbox
Here's the code I followed to get me to this stage - to me it looks the same? Can't see where I've gone wrong here

React hook form unit test

I have the following component:
const WorkProviderList: React.FC<StepComponentParams> = ({
metadata,
}): JSX.Element => {
const {
control,
setValue,
getValues,
} = useForm({
resolver: yupResolver(validationSchema),
defaultValues: { [CONFIRM_NAME]: true } as any,
mode: "all",
});
...
const isOtherFieldNameVisible = getValues()[FIELD_NAME] === "other";
console.log("isOtherFieldNameVisible: ", isOtherFieldNameVisible);
return (
<>
<Text element="h1" variant="title2Bold">
Which is your work provider?
</Text>
<Text>
If you work with more than one, select the provider that you work with
the most.
</Text>
<fieldset role="group" aria-labelledby={FIELD_NAME}>
<Controller
name={FIELD_NAME}
control={control}
render={() => (
<RadioGroupFields
fields={radioFields}
name={FIELD_NAME}
onChange={(e) => {
setValue(FIELD_NAME, e.target.value, {
shouldValidate: true,
});
}}
/>
)}
/>
</fieldset>
{isOtherFieldNameVisible && (
<div className="py-3">
<FormField
name={OTHER_FIELD_NAME}
label="What is the name of your work provider?"
>
<Controller
name={OTHER_FIELD_NAME}
control={control}
render={({ field: { onChange } }) => (
<TextField
id="other_input"
isFullWidth
name={OTHER_FIELD_NAME}
statusType="info"
onChange={onChange}
/>
)}
/>
</FormField>
</div>
)}
</>
);
};
and the following test:
const { getByLabelText, getByText } = render(
<WorkProviderList {...props} />
);
const input = getByLabelText("I work with a different work provider");
fireEvent.change(input, { target: { value: "other" } });
fireEvent.click(input);
const text = getByText("What is the name of your work provider?");
expect(text).toBeInTheDocument();
even though the console.log in the component returns true, I would expect to find the label I am looking for but I get the following error:
TestingLibraryElementError: Unable to find a label with the text of:
What is the name of your work provider?

Formik - Render ErrorMessage automatically

I have the following code which you can find here:
https://stackblitz.com/edit/react-d2fadr?file=src%2FApp.js
import { ErrorMessage, Field, Form, Formik } from 'formik';
import React from 'react';
import { Button } from 'react-bootstrap';
import * as Yup from 'yup';
let fieldName = 'hexColor';
const TextInput = ({ field, value, placeholder, handleChange }) => {
value = (field && field.value) || value || '';
placeholder = placeholder || '';
return (
<input
type="text"
placeholder={placeholder}
onChange={(e) => handleChange(e.target.value)}
value={value}
/>
);
};
export default () => {
const onSubmit = (values, { setSubmitting }) => {
console.log(values);
setSubmitting(false);
};
return (
<Formik
initialValues={{ [fieldName]: 'ff0000' }}
validationSchema={Yup.object({
hexColor: Yup.string().test(
fieldName,
'The Hex Color is Wrong.',
(value) => {
return /^[0-9a-f]{6}$/.test(value);
}
),
})}
onSubmit={onSubmit}
enableReinitialize
>
{(formik) => {
const handleChange = (value) => {
value = value.replace(/[^0-9a-f]/g, '');
formik.setFieldValue(fieldName, value);
};
return (
<Form>
<div>
<Field
component={TextInput}
name={fieldName}
placeholder="Hex Color"
handleChange={handleChange}
/>
<ErrorMessage name={fieldName} />
</div>
<Button
type="submit"
disabled={!formik.isValid || formik.isSubmitting}
>
Submit
</Button>
</Form>
);
}}
</Formik>
);
};
I want to know if is it any way to render the ErrorMessage element automatically?
The error message should be shown somewhere around the input text.
If you know how, you can fork the StackBlitz above with your suggestion.
Thanks!
Don't really know why ErroMessage is not rendering before you submit your form once but you can replace the line <ErrorMessage name={fieldName} /> by {formik.errors[fieldName]} to make it works
import { ErrorMessage, Field, Form, Formik } from 'formik';
import React from 'react';
import { Button } from 'react-bootstrap';
import * as Yup from 'yup';
let fieldName = 'hexColor';
const TextInput = ({ field, value, placeholder, handleChange }) => {
value = (field && field.value) || value || '';
placeholder = placeholder || '';
return (
<input
type="text"
placeholder={placeholder}
onChange={(e) => handleChange(e.target.value)}
value={value}
/>
);
};
export default () => {
const onSubmit = (values, { setSubmitting }) => {
console.log(values);
setSubmitting(false);
};
return (
<Formik
initialValues={{ [fieldName]: 'ff0000' }}
validationSchema={Yup.object({
hexColor: Yup.string().test(
fieldName,
'The Hex Color is Wrong.',
(value) => {
return /^[0-9a-f]{6}$/.test(value);
}
),
})}
onSubmit={onSubmit}
enableReinitialize
>
{(formik) => {
const handleChange = (value) => {
value = value.replace(/[^0-9a-f]/g, '');
formik.setFieldValue(fieldName, value);
};
return (
<Form>
<div>
<Field
component={TextInput}
name={fieldName}
placeholder="Hex Color"
handleChange={handleChange}
/>
{formik.errors[fieldName]}
</div>
<Button
type="submit"
disabled={!formik.isValid || formik.isSubmitting}
>
Submit
</Button>
</Form>
);
}}
</Formik>
);
};
the issue is validation schema. When I changed 6
return /^[0-9a-f]{6}$/.test(value);
to 3
return /^[0-9a-f]{3}$/.test(value);
and submitted with the initial value, ErrorMessage component is rendered
To reach your goal, I changed your code as below:
Since Formik's default component is Input, I deleted your TextInput component as there was nothing special in your component and handleChange function.
<Field name="hexColor" placeholder="Hex Color" onChange={(e) => handleChange(e.target.value)}/>
<ErrorMessage name="hexColor" />
As in mentioned in this answer, I changed your submit button condition to determine whether the button is disabled or not:
<Button type="submit" disabled={Object.keys(errors).length}>
Submit
</Button>
You can view my entire solution here.
Edit
If you want to keep your component, you should pass props as you might be missing something important, e.g. onChange,onBlur etc.
const TextInput = ({ field, ...props }) => {
return (
<input
{...field} {...props}
// ... your custom things
/>
);
};
<Field
component={TextInput}
name={fieldName}
placeholder="Hex Color"
onChange={(e) => handleChange(e.target.value)}
/>
Solution 2

Mock function not being called using jest and testing library on react app

I am using jest and testing library to test a form. But the mock function is not being called. And the last line expect(mockOnSubmit).toHaveBeenCalled(); always fails.
Here's my test code:
App.test.js
import { render, fireEvent, cleanup } from "#testing-library/react";
import App from "./App";
import { act } from "react-dom/test-utils";
afterEach(cleanup);
test("email and password field are clear for submit", async () => {
const mockOnSubmit = jest.fn();
const { getByPlaceholderText, getByText } = render(
<App onSubmit={mockOnSubmit} />
);
const emailNode = getByPlaceholderText(/email/i);
const passwordNode = getByPlaceholderText(/password/i);
const testEmail = "test#example.com";
const testPassword = "123123";
await act(async () => {
fireEvent.change(emailNode, {
target: { value: testEmail }
});
fireEvent.change(passwordNode, {
target: { value: testPassword }
});
});
expect(emailNode.value).toBe(testEmail);
expect(passwordNode.value).toBe(testPassword);
const submit = getByText(/submit/i);
await act(async () => {
fireEvent.click(submit);
});
expect(mockOnSubmit).toHaveBeenCalled();
});
App.js
import { useForm } from "react-hook-form";
import { Controller } from "react-hook-form";
export default function App({ onSubmit = (data) => console.log(data) }) {
const { control, handleSubmit } = useForm();
return (
<form onSubmit={handleSubmit(onSubmit)}>
<Controller
name="email"
control={control}
defaultValue=""
rules={{
required: `Email is required.`,
pattern: {
value: /^[A-Z0-9._%+-]+#[A-Z0-9.-]+\.[A-Z]{2,4}$/i,
message: "Invalid email address"
}
}}
render={({ field }) => {
return (
<input
placeholder="Email"
type="text"
key="email"
name="email"
id="email"
{...field}
/>
);
}}
/>
<Controller
name="email"
control={control}
defaultValue=""
rules={{
required: `Password is required.`
}}
render={({ field }) => {
return (
<input
placeholder="Password"
type="text"
key="password"
name="password"
id="password"
{...field}
/>
);
}}
/>
<input type="submit" value="submit" />
</form>
);
}
This minimal implementation is also available at
https://codesandbox.io/s/react-hook-form-testing-olo4i
Sidenote: This works fine without react-hook-form implementation. But as I add handleSubmit from react-hook-form it does not work anymore. Here's a working version by #Florian Motteau where it is working by bypassing react-hook-form
You are checking that the onSubmit props has been called, but in your component your are not calling this props when the form is submitted. So the mock you provide in the test will never get called. Besides this, in App.js, the onSubmit props is passed an evaluation of handleSubmit (handleSubmit()), instead you must pass a reference to the handler function (handleSubmit, or () => handleSubmit()).
Solving these two problems you get :
// App.js
export default function App({ onSubmit = (data) => console.log(data) }) {
const { control, handleSubmit } = useForm();
return (
<form onSubmit={onSubmit}>
<Box sx={{ mt: 2 }}>
<EmailField label="Email" name="email" control={control} />
</Box>
<Box sx={{ mt: 2 }}>
<PasswordField label="Password" name="password" control={control} />
</Box>
<Box sx={{ mt: 2 }}>
<Button name="submit" type="submit" variant="contained">
Submit
</Button>
</Box>
</form>
);
}
I also suggets that you use visual text to interact with DOM elements :
fireEvent.click(getByText("Submit"));
Fixed sandbox : https://codesandbox.io/s/react-hook-form-testing-forked-vs9ez?file=/src/App.js
Finally, you should not have to wrap things in act(), react-testing-library will do this for you, see https://kentcdodds.com/blog/fix-the-not-wrapped-in-act-warning.

Categories

Resources