react-hook-form when editing doesn't update - reactjs

I can't find a way to provide the existing information from the DB to the react-hook-form library to revalidate when clicking submits and to update it if the data was changed.
The problem is when clicking on submit button, this object will shown without any value for firstName and lastname:
{firstName: undefined, lastname: undefined}
Simplified version of my component:
import React from "react";
import { useForm } from "react-hook-form";
import { TextField } from "#material-ui/core";
const App = (props) => {
const { register, handleSubmit} = useForm();
const onSubmit = (data) => {
console.log(data); // ---> here
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<h2>Step 1</h2>
<label>
First Name:
<TextField {...register("firstName")} defaultValue="firstname" />
</label>
<label>
Last Name:
<TextField {...register("lastname")} defaultValue="lastname" />
</label>
<input type="submit" />
</form>
);
};
export default App
Any idea how to create a form that allows you to edit the fields?
Check out on codesandbox

The problem is with the TextFiled component. the register is not set correctly.
According to the MUI documentation:
props:inputProps type:object description:Attributes applied to the input element.
So you need to pass the register throw the inputProps:
<TextField inputProps={register("firstName")} defaultValue="firstname" />
<TextField inputProps={register("lastname")} defaultValue="lastname" />

Related

What am I misunderstanding in my boolean radio group in Formik?

Following the Radio Group Example in the Formik docs and using Bootstrap 5 I've tried to create a boolean radio component that gets passed in a name and label. However, on render with the initialValues:
<Formik
initialValues={{
foo: false,
bar: false,
}}>
the no is selected because the initial value is set to false but whenever yes is chosen the radio will not fill in, component:
import React from 'react'
import PropTypes from 'prop-types'
import { Field } from 'formik'
const RadioBool = ({ name, label }) => {
const yesId = `${name}-yes`
const noId = `${name}-no`
return (
<>
<p id={`${name}-group-label`}>{label}:</p>
<div className="form-group mb-4" role="group" aria-labelledby={`${name}-group-label`}>
<div className="form-check">
<label htmlFor={yesId}>Yes</label>
<Field id={yesId} className="form-check-input" type="radio" name={name} value={true} />
</div>
<div className="form-check">
<label htmlFor={noId}>No</label>
<Field id={noId} className="form-check-input" type="radio" name={name} value={false} />
</div>
</div>
</>
)
}
RadioBool.propTypes = {
name: PropTypes.string.isRequired,
label: PropTypes.string.isRequired,
}
export default RadioBool
but if checked is added to Yes, example:
<Field id={yesId} className="form-check-input" type="radio" name={name} value={true} checked />
the radio group works and I'm not sure why.
tests:
<RadioBool name="foo" label="Look at foo?" />
<RadioBool name="bar" label="Look at bar?" />
Research
How to add Radio Button in Formik Validations Reactjs?
How to create Radio buttons with Formik?
How to make use of Radio Group with useFormik Hook
how do you use onChange property on react formik radio field
formik radio button validation issues
Why does my boolean radio component not work in Formik unless checked is used on one <Field />?
The problem is described here Radio values must be strings, can't be numbers or booleans and as I see it's not solved 'from the box' yet.
In my TypeScript project I've created custom component. May it would be helpfull for you:
import React, { FC } from 'react';
type Props = {
name: string
value: boolean
checked: boolean
setValues: (values: React.SetStateAction<any>, shouldValidate?: boolean) => void;
}
const RadioButtonBooleanField: FC<Props> = ({name, value, checked, setValues}) => {
return (
<input type='radio' className="form-check-input" name={name} value={String(value)} checked={checked}
onChange={(e: any) => {
setValues((prevValues: any) => ({
...prevValues,
[e.target.name]: e.target.value === "true"
}))
}
}
/>
);
};
It can be used like this inside Formik component:
{({ errors, touched, values, setValues }) => (
<RadioButtonBooleanField name="IsExtendedView" value={false} checked={!values.IsExtendedView} setValues={setValues}/>
)}

react-hook-form access to validation rules in nested component

I'd like to access to the validation rules defined in react-hook-form's Resister in the nested component to dynamically display required indicator(*).
Is there a way to access from Nested component?
I don't want to repeat by passing as a prop.
<TextBox ref={register({ required: true })} label="Serial No" name="serialNo" />
const TextBox = React.forwardRef(({ label, name }, ref) => (
<>
<label htmlFor={name}>
{label} {***required*** && <span>*</span>}
</label>
<input type="text" name={name} id={name} ref={ref} />
</>
))
have a look at https://react-hook-form.com/api#useFormContext
import React from "react";
import { useForm, FormProvider, useFormContext } from "react-hook-form";
export default function App() {
const methods = useForm();
const onSubmit = data => console.log(data);
return (
<FormProvider {...methods} > // pass all methods into the context
<form onSubmit={methods.handleSubmit(onSubmit)}>
<NestedInput />
<input type="submit" />
</form>
</FormProvider>
);
}
function NestedInput() {
const { register } = useFormContext(); // retrieve all hook methods
return <input name="test" ref={register} />;
}
which allow you to access all hook form methods in the nested component level.

Adding a form to Gatsby JS, with an existing template that is export default

I am attempting to follow this tutorial to add a form to Gatsby JS. I understand it if my file wasn't setup differently. Firstly the tutorials component starts like this
export default class IndexPage extends React.Component {
Where I have this
export default ({ data }) => (
Then I am asked to place the following inside of it. I tried with both the render and return portion, and without.
state = {
firstName: "",
lastName: "",
}
handleInputChange = event => {
const target = event.target
const value = target.value
const name = target.name
this.setState({
[name]: value,
})
}
handleSubmit = event => {
event.preventDefault()
alert(`Welcome ${this.state.firstName} ${this.state.lastName}!`)
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
First name
<input
type="text"
name="firstName"
value={this.state.firstName}
onChange={this.handleInputChange}
/>
</label>
<label>
Last name
<input
type="text"
name="lastName"
value={this.state.lastName}
onChange={this.handleInputChange}
/>
</label>
<button type="submit">Submit</button>
</form>
)
}
Here is all my code without the render and return portion
import React from 'react'
import { HelmetDatoCms } from 'gatsby-source-datocms'
import { graphql } from 'gatsby'
import Layout from "../components/layout"
export default ({ data }) => (
<Layout>
state = {
firstName: "",
lastName: "",
}
handleInputChange = event => {
const target = event.target
const value = target.value
const name = target.name
this.setState({
[name]: value,
})
}
handleSubmit = event => {
event.preventDefault()
alert(`Welcome ${this.state.firstName} ${this.state.lastName}!`)
}
<form onSubmit={this.handleSubmit}>
<label>
First name
<input
type="text"
name="firstName"
value={this.state.firstName}
onChange={this.handleInputChange}
/>
</label>
<label>
Last name
<input
type="text"
name="lastName"
value={this.state.lastName}
onChange={this.handleInputChange}
/>
</label>
<button type="submit">Submit</button>
</form>
<article className="sheet">
<HelmetDatoCms seo={data.datoCmsPricing.seoMetaTags} />
<section className="left-package-details">
<h1 className="sheet__title">{data.datoCmsPricing.title}</h1>
<p>
<span>${data.datoCmsPricing.priceAmount}</span> | <span>{data.datoCmsPricing.lengthOfSession}</span>
</p>
{data.datoCmsPricing.details.map(detailEntry => { return <li key={detailEntry.id}> {detailEntry.task}</li>})}
<p>
{data.datoCmsPricing.numberOfSessions}
</p>
book
<p>{data.datoCmsPricing.minimumMessage}</p>
</section>
<section className="right-package-details">
<img src={data.datoCmsPricing.coverImage.url} />
<div
className=""
dangerouslySetInnerHTML={{
__html: data.datoCmsPricing.descriptionNode.childMarkdownRemark.html,
}}
/>
</section>
</article>
</Layout>
)
export const query = graphql`
query WorkQuery($slug: String!) {
datoCmsPricing(slug: { eq: $slug }) {
seoMetaTags {
...GatsbyDatoCmsSeoMetaTags
}
title
priceAmount
details{
task
}
lengthOfSession
numberOfSessions
minimumMessage
descriptionNode {
childMarkdownRemark {
html
}
}
coverImage {
url
}
}
}
`
and the error I get is
There was a problem parsing "/mnt/c/Users/Anders/sites/jlfit-cms/src/templates/pricingDetails.js"; any GraphQL
fragments or queries in this file were not processed.
This may indicate a syntax error in the code, or it may be a file type
that Gatsby does not know how to parse.
File: /mnt/c/Users/Anders/sites/jlfit-cms/src/templates/pricingDetails.js
The problem you are facing is because you are trying to use state (and setState) on a functional component when the example uses a class.
Functional components don't have the same tools/syntax/APIs available to you as a class component (for better or worse) so you have to ensure you're using the correct approach for each case.
In the most recent versions of React you can have the equivalent of state and setState made available to you by using React hooks, more specifically the useState hook.
I've put together a quick working example of the code you pasted in your question converted to React hooks. You can find it on this sandbox.
I recommend you have a read over the initial parts of the React docs to ensure you're familiar with the foundational concepts or React, it will save a lot of headache in the future. 🙂

Formik Material UI and react testing library

I am finding it hard to work with react testing library and understand the queries I need to use in order to select the components I need to test. The queries are too simplistic when the DOM gets more and more verbose with frameworks like material ui and formik.
I have created a code sandbox to illustrate the problem. You can check the failing test there.
https://codesandbox.io/embed/px277lj1x
The issue I am getting is, the query getLabelTextBy() does not return the component. It looks like the aria label by or the for attribute is not being rendered by material ui. Not sure how to fix this error.
The code is below here for reference too
//Subject under test
import React from "react";
import { Button } from "#material-ui/core";
import { TextField } from "formik-material-ui";
import { Field, Form, Formik } from "formik";
import * as yup from "yup";
const validationSchema = yup.object().shape({
name: yup.string().required("Office name is required"),
address: yup.string().required("Address is required")
});
export default () => (
<Formik
initialValues={{
name: "",
address: ""
}}
validationSchema={validationSchema}
onSubmit={(values, { setSubmitting }) => {
setSubmitting(false);
console.log("form is submitted with", values);
}}
render={({ submitForm, isSubmitting, isValid }) => (
<Form>
<Field
label="Office Name"
name="name"
required
type="text"
component={TextField}
/>
<Field
label="Address Line 1"
name="addressLine1"
type="text"
component={TextField}
/>
<Button
variant="contained"
color="primary"
fullWidth={false}
size="medium"
disabled={isSubmitting || !isValid}
onClick={submitForm}
data-testid="submitButton"
>
Submit
</Button>
</Form>
)}
/>
);
// Test
import React from "react";
import { render, fireEvent } from "react-testing-library";
import App from "./App";
describe("Create Office Form tests", () => {
it("button should not be disabled when all required fields are filled up", () => {
const { getByLabelText, getByTestId, debug } = render(<App />);
const values = {
"office name": "office",
address: "address 1"
};
for (const key in values) {
const input = getByLabelText(key, { exact: false, selector: "input" });
fireEvent.change(input, { target: { value: values[key] } });
}
const button = getByTestId("submitButton");
expect(button.disabled).not.toBeDefined();
});
});
You must add an id to your Field because the label's for attribute expect the ID of the input element it refers to:
<Field
id="myName"
label="Office Name"
name="name"
required
type="text"
component={TextField}
/>
A working example:
Few things. The use of { getByLabelText, getByTestId, debug } is not advised by the creator of the library. You should use screen.getByLabelText().
If there is a change, it's possible that it doesn't wait for the new render, so it would be better to await it or wrap the expect in a waitFor method.
Also, expect(button.disabled).not.toBeDefined() should not be correct. I think you're just checking if the button is disabled or not, right?
So you can use expect(button).toBeDisabled() or not.toBeDisabled()
At least to test it I think you should take off the for loop and check it normally. You can use screen.debug(component) to have the HTML shown after some action.

Refactoring almost identical components, formik react

I have two identical components, and only few differences (one). There are two many repetitive code and boilerplate, but I am unsure how to refactor this so that I only need to supply a config probably.
LoginPage.js
import React from 'react';
import { Link } from 'react-router-dom';
import { Helmet } from 'react-helmet';
import { Formik, FastField, Form, ErrorMessage } from 'formik';
import PropTypes from 'prop-types';
import { FormDebug } from 'utils/FormDebug';
import { LoginValidationSchema } from 'validations/AuthValidationSchema';
function LoginPage({ username, onChangeUsername, onSubmitForm }) {
return (
<div>
<Helmet>
<title>Login</title>
</Helmet>
<Formik
initialValues={{ username, password: '' }}
validationSchema={LoginValidationSchema}
onSubmit={onSubmitForm}
render={({ isSubmitting, isValid, handleChange }) => (
<Form>
<FastField
type="text"
name="username"
render={({ field }) => (
<input
{...field}
onChange={e => {
handleChange(e);
onChangeUsername(e);
}}
/>
)}
/>
<ErrorMessage name="username" component="div" aria-live="polite" />
<FastField type="password" name="password" />
<ErrorMessage name="password" component="div" aria-live="polite" />
<button type="submit" disabled={isSubmitting || !isValid}>
Login
</button>
<FormDebug />
</Form>
)}
/>
<Link to="/auth/forgot_password">Forgot Password</Link>
</div>
);
}
LoginPage.propTypes = {
username: PropTypes.string,
onSubmitForm: PropTypes.func.isRequired,
onChangeUsername: PropTypes.func.isRequired,
};
export default LoginPage;
ForgotPasswordPage.js
import React from 'react';
import { Helmet } from 'react-helmet';
import { Formik, FastField, Form, ErrorMessage } from 'formik';
import PropTypes from 'prop-types';
import { FormDebug } from 'utils/FormDebug';
import { ForgotPasswordValidationSchema } from 'validations/AuthValidationSchema';
function ForgotPasswordPage({ username, onChangeUsername, onSubmitForm }) {
return (
<div>
<Helmet>
<title>Forgot Password</title>
</Helmet>
<Formik
initialValues={{ username }}
validationSchema={ForgotPasswordValidationSchema}
onSubmit={onSubmitForm}
render={({ isSubmitting, isValid, handleChange }) => (
<Form>
<FastField
type="text"
name="username"
render={({ field }) => (
<input
{...field}
onChange={e => {
handleChange(e);
onChangeUsername(e);
}}
/>
)}
/>
<ErrorMessage name="username" component="div" aria-live="polite" />
<FormDebug />
<button type="submit" disabled={isSubmitting || !isValid}>
Reset Password
</button>
</Form>
)}
/>
</div>
);
}
ForgotPasswordPage.propTypes = {
username: PropTypes.string,
onSubmitForm: PropTypes.func.isRequired,
onChangeUsername: PropTypes.func.isRequired,
};
export default ForgotPasswordPage;
How to you refactor this, if you were me.
I am thinking HOC., but I am unsure how to call pass the "children" which is different
Apologies if you're not looking for a general answer, but I don't think you'll improve maintainability by generalising what may just be seemingly correlated components. I expect these components will drift further apart as you mature your application, e.g. by adding social login, option to 'remember me', captchas, option for retrieving both username and password by email, different handling of an unknown username when retrieving password vs signing in, etc. Also, this is a part of your component you really don't want to get wrong, so KISS and all. Finally, consider if there really is a third use case for such a semi-generalised login-or-retrieve-password form component.
Still, minor improvement could be made by e.g. creating a reusable UsernameField component, the usage of which will be simple and consistent to both cases. Also consider a withValidation HOC adding an error message to a field. If you really want to stretch it, you could have a withSubmit HOC for Formik, passing all props to Formik, rendering children (which you would pass handleChange prop) and a submit button. I assume form itself uses context to pass state to ErrorMessage and FastField.
I may be missing some complexity not stated here, but this looks as simple as creating a generic Functional Component that accepts a couple more passed-in properties. I did a diff and you would only need to add 'title', 'buttonText', and, if you like, 'type' to your props, for sure. You could also send initialValues object as a prop, instead of deriving it from 'type'.
I mean, did you try the following?
import React from 'react';
import { Link } from 'react-router-dom';
import { Helmet } from 'react-helmet';
import { Formik, FastField, Form, ErrorMessage } from 'formik';
import PropTypes from 'prop-types';
import { FormDebug } from 'utils/FormDebug';
import * as schema from 'validations/AuthValidationSchema';
function AuthPage({ buttonText, initialValues, title, type, username,
onChangeUsername, onSubmitForm }) {
const authSchema = type === 'login'
? schema.LoginValidationSchema
: schema.ForgotPasswordValidationSchema;
return (
<div>
<Helmet>
<title>{title}</title>
</Helmet>
<Formik
initialValues={initialValues}
validationSchema={authSchema}
onSubmit={onSubmitForm}
render={({ isSubmitting, isValid, handleChange }) => (
<Form>
<FastField
type="text"
name="username"
render={({ field }) => (
<input
{...field}
onChange={e => {
handleChange(e);
onChangeUsername(e);
}}
/>
)}
/>
<ErrorMessage name="username" component="div" aria-live="polite" />
{type === 'forgot' &&
<FastField type="password" name="password" />
<ErrorMessage name="password" component="div" aria-live="polite" />
}
<button type="submit" disabled={isSubmitting || !isValid}>
{buttonText}
</button>
<FormDebug />
</Form>
)}
/>
<Link to="/auth/forgot_password">Forgot Password</Link>
</div>
);
}
AuthPage.propTypes = {
buttonText: PropTypes.string,
initialValues: PropTypes.object,
title: PropTypes.string,
type: PropTypes.oneOf(['login', 'forgot'])
username: PropTypes.string,
onSubmitForm: PropTypes.func.isRequired,
onChangeUsername: PropTypes.func.isRequired,
};
export default AuthPage;
(Only thing I can't remember is whether the conditional render of password field and its ErrorMessage needs to be wrapped in a div or not to make them one element)
If you don't want to pass in initial values:
const initialVals = type === 'login'
? { username, password: ''}
: { username }
...
initialValues={initialVals}
and remove it from propTypes
Only other thing I'm not sure of is why FormDebug is placed differently in the two versions. I've left it after the button.
Those two pages have separate concerns, so I wouldn't suggest pulling logic that can be used for both cases as that tends to add complexity by moving code further apart even if it does remove a few duplicate lines. In this case a good strategy may be to think of things that you reuse within components.
For example you can wrap the formik component in your own wrapper and then wrap for example an input component that works with your new form. A good exercise in reducing boiler plate could be to aim for some variant of this end result
<CoolForm
initialValues={{ username, password: '' }}
validationSchema={LoginValidationSchema}
>
<CoolFormInputWithError someProps={{howeverYou: 'want to design this'}}>
<CoolFormInputWithError someProps={{howeverYou: 'want to design this'}}>
<CoolFormSubmit>
Login
</CoolFormSubmit>
<FormDebug />
</CoolForm>
That's just an idea, but the nice thing about this strategy is that if you want to refactor formik out for whatever reason, its really simple because all your code now is wrapped in CoolForm components, so you only end up changing the implementation of your own components, although that benefit in this case is very small.

Resources