I want to write the unit test for React formik form fields.
Related packages versions are bellow
React version ^16.9.0
Formik version ^1.5.8
Jest version ^23.0.0
jest-enzyme version ^7.1.2
Enzyme version ^3.11.0
Component.js
const formInitialValues = {
frequency: null,
weekType: null'
};
<Formik
enableReinitialize
initialValues={formInitialValues}
onSubmit={(values, actions) => {
console.log(values);
}}
validate={values => {
// validation here
}}
render={props => (
<Form
onSubmit={props.handleSubmit}
data-test="smart-cabinet-add-doc-form"
>
<Row>
<div className="form-group col-md-6 ">
<label htmlFor="frequency" data-test="frequency-label">
Frequency
</label>
<CustomSelect
id="frequency"
name="frequency"
className="form-control col-sm-10"
options={SmartCabinetHelper.ADD_DOCUMENT_FREQUENCY_OPTIONS}
onChange={(name, value) => {
setFieldValue(name, value);
formatDueDate(values);
}}
onBlur={setFieldTouched}
placeholder="Select Frequency"
setFieldValue={setFieldValue}
value={values.frequency}
isDisabled={isNull(values.uploadedBy)}
data-test="frequency-field"
/>
<div className="error-message">
<ErrorMessage name="frequency" />
</div>
</div>
</Row>
{!isNull(values.frequency) && (
<Row>
<div className="form-group col-md-12 ">
<CustomSelect
id="weekType"
name="weekType"
className="form-control"
options={QuickLinkModalHelper.WEEKS_TYPES.slice(0, -1)}
onChange={(name, value) => {
setFieldValue(name, value);
formatDueDate(values);
}}
onBlur={setFieldTouched}
isSearchable={false}
placeholder=""
setFieldValue={setFieldValue}
value={values.weekType}
data-test="weekType-field"
/>
</div>
</Row>
)}
</form>
)}
/>
In the componentTest.js file I can test the frequency element, but I can't test the weekType element because that element depends on the frequency value.
{!isNull(values.frequency) && (
When the unit test runs, according to the formInitialValues, of formik form frequency value is Null. So I can't check weekType, It occurs an error. Test fails.
ComponentTest.js
const setup = () => {
return mount(
<Component />
);
};
describe('Test form elements', () => {
let wrapper;
beforeEach(() => {
wrapper = setup(defaultProps);
});
test('test frequency field', () => {
const frequencyField = findByTestAttr(
wrapper,
'frequency-field'
);
expect(frequencyField.length).toBe(1); // This test passes
});
test('test weekType field', () => {
const weekTypeField = findByTestAttr(
wrapper,
'weekType-field'
);
expect(weekTypeField.length).toBe(1); // This test fails, because frequency value = null
});
});
I tried too many ways to figure that. But couldn't. Is there any way to change formInitialValues in the testing file? Thank you.
I tried many ways to find a solution, finally found a way
Use mock formik form in test file except importing component.js using react-testing library
component.test.js
describe('Test form elements', () => {
it('test frequency field', async () => {
const { getByText, getByTestId } = render(
<Formik
initialValues={{
frequency: null,
weekType: null' }}
onSubmit={mock}
>
<Form
onSubmit={props.handleSubmit}
data-test="smart-cabinet-add-doc-form"
>
<Row>
<div className="form-group col-md-6 ">
<label htmlFor="frequency" data-test="frequency-label">
Frequency
</label>
<CustomSelect
id="frequency"
name="frequency"
...
/>
<div className="error-message">
<ErrorMessage name="frequency" />
</div>
</div>
</Row>
{!isNull(values.frequency) && (
<Row>
<div className="form-group col-md-12 ">
<CustomSelect
id="weekType"
name="weekType"
...
data-test="weekType-field"
/>
</div>
</Row>
)}
</form>
</Formik>
);
const weekTypeField = await waitForElement(() => getByTestId('weekType-field'));
expect(weekTypeField.length).toBe(1);
});
});
Any suggestions...
Thank you.
Related
I have a formik form where I have used react-select for select list. Here is my form:
import React from "react";
import { ErrorMessage, Field, Form, Formik } from "formik";
import * as Yup from "yup";
import { Button, Col, FormGroup } from "reactstrap";
import Select from "react-select";
const AddBankForm = (props) => {
return (
<Formik
initialValues={{
district: props.districts,
}}
validationSchema={Yup.object({
district: Yup.string().required("Required"),
})}
onSubmit={(values, actions) => {
setError(null);
setMessage(null);
try {
const response = await postDataWithAuth(DISTRIBUTOR_BANK_ADD, {
routing_number: values.branch,
bank_account_number: values.accountNumber,
account_holder_name: values.accountName,
pin_number: values.tpin,
});
// This is not working
actions.resetForm();
setMessage(response.message);
} catch (e) {
setError(e.response.data);
}
actions.setSubmitting(false);
}}
>
{(formikProps) => (
<Form onSubmit={formikProps.handleSubmit} autoComplete="one-time-code">
<div className="form-row">
<Col>
<FormGroup>
<label>
District<span className="text-danger">*</span>
</label>
<Select
menuPortalTarget={document.body}
type="text"
name="district"
onChange={(option) => {
props.updateDistrict(option.value);
formikProps.setFieldValue("district", option.value);
}}
options={
props.isCreateLiftingSuccessful ? [] : props.districts
}
onBlur={formikProps.handleBlur}
/>
<ErrorMessage
name="district"
component="div"
className="text-danger"
/>
</FormGroup>
</Col>
</div>
<div className="form-row mt-3 text-center">
<Col>
<Button
className="btn btn-success"
type="submit"
disabled={!formikProps.dirty || formikProps.isSubmitting}
>
Submit
</Button>
</Col>
</div>
</Form>
)}
</Formik>
);
};
The problem is that the react-select field is not getting cleared after the form submission. I have used formik's resetForm() method to clear my form. But it seems that resetForm method does not have any impact on the react-select field.
You can use 'ref' props for clear react-select field.
import React from "react";
import { ErrorMessage, Field, Form, Formik } from "formik";
import * as Yup from "yup";
import { Button, Col, FormGroup } from "reactstrap";
import Select from "react-select";
const AddBankForm = (props) => {
// update start
let selectRef = null;
const clearValue = () => {
selectRef.select.clearValue();
};
// update end
return (
<Formik
initialValues={{
district: props.districts,
}}
validationSchema={Yup.object({
district: Yup.string().required("Required"),
})}
onSubmit={(values, actions) => {
setError(null);
setMessage(null);
try {
const response = await postDataWithAuth(DISTRIBUTOR_BANK_ADD, {
routing_number: values.branch,
bank_account_number: values.accountNumber,
account_holder_name: values.accountName,
pin_number: values.tpin,
});
// This is not working
actions.resetForm();
// Try this way
clearValue();
setMessage(response.message);
} catch (e) {
setError(e.response.data);
}
actions.setSubmitting(false);
}}
>
{(formikProps) => (
<Form onSubmit={formikProps.handleSubmit} autoComplete="one-time-code">
<div className="form-row">
<Col>
<FormGroup>
<label>
District<span className="text-danger">*</span>
</label>
<Select
// use ref
ref={ref => {
selectRef = ref;
}}
menuPortalTarget={document.body}
type="text"
name="district"
onChange={(option) => {
props.updateDistrict(option.value);
formikProps.setFieldValue("district", option.value);
}}
options={
props.isCreateLiftingSuccessful ? [] : props.districts
}
onBlur={formikProps.handleBlur}
/>
<ErrorMessage
name="district"
component="div"
className="text-danger"
/>
</FormGroup>
</Col>
</div>
<div className="form-row mt-3 text-center">
<Col>
<Button
className="btn btn-success"
type="submit"
disabled={!formikProps.dirty || formikProps.isSubmitting}
>
Submit
</Button>
</Col>
</div>
</Form>
)}
</Formik>
);
};
I need to do a form in react.js but when I try to type something in the form field, It doesn't let me type anything. I create a function for the input e my form. what am i doing wrong? please help me. thanks.
renderInput(title, value, onChange, validateField, placeholder) {
return (
<div className="col-12 col-md-4">
<div className="form-groud">
<label>{title}</label>
<input type="text" className="form-control"
name={title}
value={value}
onChange={e => onChange(e)}
onBlur={validateField}
placeholder={placeholder} />
</div>
</div>
)
}
renderForm() {
return (
<div className="form">
<div className="row">
{this.renderInput("Avatar", this.state.member.avatar, e => this.updateField(e), this.validateField, "profile picture" )}
{this.renderInput("Name", this.state.member.name, e => this.updateField(e), this.validateField, "name" )}
{this.renderInput("Email", this.state.member.email, e => this.updateField(e), this.validateField, "email" )}
{this.renderInput("Project", this.state.member.project, e => this.updateField(e), this.validateField, "project" )}
{this.renderInput("Devices", this.state.member.devices, e => this.updateField(e), this.validateField, "devices" )}
{this.renderInput("MainStack", this.state.member.mainstack, e => this.updateField(e), this.validateField,"main stack" )}
</div>
<hr />
<div className="row">
<div className="col-12 d-flex justify-content-end">
<button className="btn btn-primary"
onClick={e => this.save(e)}>
Save
</button>
<button className="btn btn-secondary ml-2"
onClick={e => this.clear(e)}>
Cancel
</button>
</div>
</div>
</div>
)
}
My updateField method:
updateField (event) {
const member = {...this.state.member}
member[event.target.avatar] = event.target.value
member[event.target.name] = event.target.value
member[event.target.project] = event.target.value
member[event.target.devices] = event.target.value
member[event.target.mainstack] = event.target.value
this.setState({ member })
}
you should controll input form value's using react state, in React is called a “controlled component”. So check your updateField function and make sure that it change the state value.
https://reactjs.org/docs/forms.html
I Am populating values of my input field from JSON data what am getting from back-end, now there is an edit button on UI by on click on that button I am enabling my input field but not able to type inside as I am setting some value
I want to write inside the input once I have made them editable.
const { register, handleSubmit, errors } = useForm();
const [disabled, setdisabled] = useState(false);
const [editBtn, seteditBtn] = useState(true);
<form onSubmit={handleSubmit(onSubmit)}>
{editBtn === true && (
<div align="right">
<button
className="btn white_color_btn"
type="button"
onClick={edit}
>
Edit
</button>
</div>
)}
{editBtn === false && (
<button className="btn white_color_btn" type="submit">
Save
</button>
)}
<div className="row">
<div className="form-group col-6 col-sm-6 col-md-6 col-lg-4 col-xl-4">
<input
type="text"
disable
id="firstName"
name="firstName"
value={dataItems.firstname}
disabled={disabled ? "" : "disabled"}
ref={register({ required: true })}
/>
{errors.firstname && (
<span className="text-danger">first name required</span>
)}
<br />
<label htmlFor="emp_designation">First name</label>
</div>
<div className="form-group col-6 col-sm-6 col-md-6 col-lg-4 col-xl-4">
<input
type="text"
disabled
id="lastname"
name="lastname"
value={dataItems.lastname}
disabled={disabled ? "" : "disabled"}
ref={register({ required: true })}
/>
{errors.lastname && (
<span className="text-danger">last name required</span>
)}
<br />
<label htmlFor="lastname">Lastname</label>
</div>
</div>
</form>
On click of edit
const edit = () => {
setdisabled(true);
};
Code sandbox
You need to make your input as a controlled component and write onChange handlers which will update the state. This will allow you to edit the input field values. Demo
const [disabled, setdisabled] = useState(false);
const [name, setName] = useState(empData.item.name) // setting default name
const [lastname, setLastname] = useState(empData.item.lastname) // default lastname
const edit = () => {
setdisabled(true);
};
return (<div className="container-fluid">
<div align="right">
<button className="btn" onClick={edit}>
Edit
</button>
</div>
<div className="row">
<div>
<input
type="text"
disable
id="item.value"
value={name}
onChange={(e) => {
setName(e.target.value)
}}
disabled={disabled ? "" : "disabled"}
/>
<br />
<label htmlFor="name">Name</label>
</div>
<div>
<input
type="text"
disabled
id={"lastname"}
value={lastname}
onChange={(e) => {
setLastname(e.target.value)
}}
disabled={disabled ? "" : "disabled"}
/>
<br />
<label htmlFor="lastname">Lastname</label>
</div>
</div>
</div>);
Your input is controlled by the value you are giving to it. ie: Its value is always for example empData.item.name.
And you are not providing a change handler to handle the change.
Try adding something like this:
function myChangeHandler(e){
setEditedValueSomeHow(e.target.value);
}
<input
// ...
onChange={myChangeHandler}
/>
Read more about uncontrolled components
PS: you should have had a warning message in your console like this one:
Edit:
You are using react-hook-form to manage your form but at the same time giving values to your inputs.
Please refer to this link to initialize your form values.
short story:
Remove value form your input.
Pass an object to useForm hook containing initial values.
const { register, handleSubmit, errors } = useForm({
defaultValues: {
firstName: "steve",
lastname: "smith"
}
});
Here is a working fork for your codesandbox
In order to make the input editable, you need to update a local state which controlls the input value. As suggested by you in the comments, you are using graphql to get the data, you can make use of useEffect to set the data in state and then on click of edit, update the localState
export default function App() {
const { register, handleSubmit, errors } = useForm();
const [disabled, setdisabled] = useState(true);
const [editBtn, seteditBtn] = useState(true);
const { loading, data } = useQuery("some qraphql query here"); // getting data from graphql
const [formData, setFormData] = useState({});
useEffect(() => {
setFormData(data);
}, [data]);
const edit = () => {
setdisabled(false);
seteditBtn(false);
};
const onSubmit = () => {
console.log(formData);
// submit data using formData state.
};
const handleChange = e => {
const name = e.target.name;
const value = e.target.value;
console.log(name, value);
setFormData(prev => ({ ...prev, [name]: value }));
};
return (
<div className="container-fluid">
<form onSubmit={handleSubmit(onSubmit)}>
{editBtn === true && (
<div align="right">
<button
className="btn white_color_btn"
type="button"
onClick={edit}
>
Edit
</button>
</div>
)}
{editBtn === false && (
<button className="btn white_color_btn" type="submit">
Save
</button>
)}
<div className="row">
<div className="form-group col-6 col-sm-6 col-md-6 col-lg-4 col-xl-4">
<input
type="text"
id="firstname"
name="firstname"
onChange={handleChange}
value={formData.firstname}
disabled={disabled}
ref={register({ required: true })}
/>
{errors.firstname && (
<span className="text-danger">first name required</span>
)}
<br />
<label htmlFor="emp_designation">First name</label>
</div>
<div className="form-group col-6 col-sm-6 col-md-6 col-lg-4 col-xl-4">
<input
type="text"
id="lastname"
name="lastname"
value={formData.lastname}
onChange={handleChange}
disabled={disabled}
ref={register({ required: true })}
/>
{errors.lastname && (
<span className="text-danger">last name required</span>
)}
<br />
<label htmlFor="lastname">Lastname</label>
</div>
</div>
</form>
</div>
);
}
Working mock demo
Error Message:
Error: Cannot read property 'map' of undefined
React component:
const checkBox = props => {
return (
<div>
<label htmlFor={props.name} className='form-label'>
{props.title}
</label>
<div className='checkbox-group'>
{
props.options.map(option => {
return (
<label key={option}>
<input
className='form-checkbox'
id={props.name}
name={props.name}
onChange={props.handleChange}
value={option}
checked={props.selectedOptions.indexOf(option) > -1}
type='checkbox'
/>{' '}
{option}
</label>
)
})
}
</div>
</div>
)
}
In my code is anything going wrong? If yes please let me know. Can someone help me out from this?
The error
Cannot read property 'map' of undefined
Is thrown when the map function in the comment list component is executed.
A question comment references a tutorial showing how to build a form and its sub-components. A couple of the components include examples of how to pass props, but that's missing from <Checkbox/>.
To fill that gap, here's an example of how you might expect to use <Checkbox/>. I didn't read the article in its entirety, so I'm hoping you can help correct any mistakes I've made here and it gets you started on your own development.
<Checkbox
title={'Skills'}
name={'skills'}
options = {this.state.skills} <!-- array of skills or empty array -->
selectedOptions = {this.state.newUser.skills} <!-- array of skills or empty array -->
handleChange = {this.handleInput}
/>
You could also use a simple fallback like.
const checkBox = props => {
const { options, name, titlem, selectedOptions, handleChange } = props
return (
<div>
<label for={name} className="form-label">
{title}
</label>
<div className="checkbox-group">
{(options || []).map(option => {
return (
<label key={option}>
<input
className="form-checkbox"
id={name}
name={name}
onChange={handleChange}
value={option}
checked={selectedOptions.indexOf(option) > -1}
type="checkbox"
/>{" "}
{option}
</label>
);
})}
</div>
</div>
);
};
const checkBox = ({ options, name, title, selectedOptions, handleChange }) => {
return (
<div>
<label htmlFor={name} className='form-label'>
{title}
</label>
<div className='checkbox-group'>
{
options && options.map(option => {
return (
<label key={option}>
<input
className='form-checkbox'
id={name}
name={name}
onChange={handleChange}
value={option}
checked={selectedOptions.indexOf(option) > -1}
type='checkbox'
/>{' '}
{option}
</label>
)
})
}
</div>
</div>
)
}
I have this code, but I can't make it work. The input lines simply won't accept anything. I tried searching all over the place to no avail, so i decided to finally ask the question.
P.S. I am new to react
class App extends React.Component {
state = { inputValue: [{item:'', name:''}] }
handleChange = e => {
const newValue = [...this.state.inputValue];
newValue[0][e.target.name] = e.target.value;
this.setState({inputValue: newValue});
}
render(){
return(
<div className='container jumbotron'>
<div className="row">
<div className="col">
<FirstInput handleChange={this.handleChange} inputValue={this.state.inputValue[0].name}/>
</div>
<div className="col">
<SecondInput handleChange={this.handleChange} inputValue={this.state.inputValue[0].name}/>
</div>
</div>
</div>
);
}
}
const FirstInput = (props) => (
<div>
<label>First Input</label>
<input className="form-control" onChange={props.handleChange} value={props.inputValue}/>
</div>
)
const SecondInput = ({inputValue, handleChange}) => (
<div>
<label>Second Input</label>
<input className="form-control" onChange={handleChange} value={inputValue}/>
</div>
)
ReactDOM.render(<App />, document.getElementById('root'));
Sorry, I forgot to mention that I want to maintain the array as an array of object. The goal is to have first input and second input be the same value. Meaning, changing one input will make the other input the same.
You don't have name attribute defined on your Input elements and hence the value doesn't change. Update your code to
class App extends React.Component {
state = { inputValue: [{ item: '', name: '' }, { item: '', name: '' }] }
handleChange = e => {
const newValue = [...this.state.inputValue];
newValue[0][e.target.name] = e.target.value;
this.setState({ inputValue: newValue });
}
render() {
return (
<div className='container jumbotron'>
<div className="row">
<div className="col">
<FirstInput handleChange={this.handleChange} inputValue={this.state.inputValue[0].name} />
</div>
<div className="col">
<SecondInput handleChange={this.handleChange} inputValue={this.state.inputValue[0].item} />
</div>
</div>
</div>
);
}
}
const FirstInput = (props) => (
<div>
<label>First Input</label>
<input className="form-control" name="name" onChange={props.handleChange} value={props.inputValue} />
</div>
)
const SecondInput = ({ inputValue, handleChange }) => (
<div>
<label>Second Input</label>
<input className="form-control" name="item" onChange={handleChange} value={inputValue} />
</div>
)
You are overwriting your state. inputValue: [{item:'', name:''}] is an array, and handleChange you try to assign string value.
Your code should look like this one:
class App extends React.Component {
state = {
firstInput: '',
secondInput: ''
}
handleChange = e => {
this.setState({
[e.target.name]: e.target.value;
});
}
render(){
return(
<div className='container jumbotron'>
<div className="row">
<div className="col">
<Input
label="First Input"
name="firstInput"
handleChange={this.handleChange}
inputValue={firstInput}/>
</div>
<div className="col">
<Input
label="First Input"
name="secondInput"
handleChange={this.handleChange}
inputValue={secondInput}/>
</div>
</div>
</div>
);
}
}
const Input = (props) => (
<div>
{props.label && <label>{props.label}</label>}
<input
className="form-control"
onChange={props.handleChange}
name={props.name}
value={props.inputValue}/>
</div>
)
ReactDOM.render(<App />, document.getElementById('root'));