Formik Field Wrapper - reactjs

I have a Formik form where I am trying to render a Formik field. However, the field is a currency field and onBlur is triggered I want to clean up the field format. I am attempting to wrap the Formik field around a TextField. To do so, in Form.js I have:
<Form>
<Field
component={ MoneyField }
name='cover'
variant='outlined'
label='Cover'
fullWidth
InputProps={{ startAdornment: <InputAdornment position='start'>$</InputAdornment> }} />
<Button
type='submit'
variant='contained'
color='primary'
fullWidth>
Submit
</Button>
Then my custom money field component MoneyField.js:
const MoneyField = ({ field, form, ...props }) =>{
const { name, value } = field
function formatInputToMoney(e) {
let cover = e.target.value.match(/\d+\.?/g)
cover = cover === null ? 0 : parseFloat(cover.join(''))
form.values[name] = formatMoney(cover).replace('$', '')
}
return (
<TextField
onBlur={ formatInputToMoney }
{ ...props } />
)
}
export default MoneyField
The above works and when a user changes the input the value in the form gets updated. However, if I don't have the form.values[name] line where I call the form to directly update its value, the value would get updated in the input box but not in the form object, so if I change the value to 5 in the input field I would see that change but when the form is submitted the initial value of 0 would get passed.
Is there a more efficient way to create a subcomponent of Field where the form values update based on the subcomponent without calling the form thats passed by Formik directly?

You need to call setFieldValue on the formik form in order to set field value
https://jaredpalmer.com/formik/docs/api/formik#setfieldvalue-field-string-value-any-shouldvalidate-boolean-void in Formik's form which gets submitted. Once formik updates form model, it will cause re-render of your component which will display updated value to you.
const MoneyField = ({ field, form, ...props }) =>{
const { name, value } = field
function formatInputToMoney(e) {
let cover = e.target.value.match(/\d+\.?/g)
cover = cover === null ? 0 : parseFloat(cover.join(''))
form.setFieldValue(name, formatMoney(cover).replace('$', ''));
}
return (
<TextField
onBlur={ formatInputToMoney }
{ ...props } />
)
}
export default MoneyField

Related

Passing value to hidden input from dropdown menu in react

I have react-select dropdown menu and hidden input which I pass to form when submiting...
using useState hook I created variable which tracks changes to react-select dropdown menu.
Hidden input has this variable as value also. I thought this would be enough.
But when I submit the form, console. log shows me that value of input is empty despite that variable that was selected from dropdown menu is actually updated.
I mean variable that I have chosen console logs some value, but hidden input thinks that it is still empty.
Does it means I have to rerender manually page each time I change that variable so input gets it's new value using useEffect ? Which is bad solution for me, I don't like it, thought it would be done automatically.
Or instead of useState I must create and use variable via Redux ? Which I also don't like, use redux for such small thing fills overcomplicated.
Isn't there any nice elegant solution ? :)
import { useForm } from 'react-hook-form';
const [someVar,setSomeVar]=useState('');
const {
register,
handleSubmit,
formState: { errors },
} = useForm({ mode: 'onBlur' });
const handleFormSubmit = (data) => {
console.error('success');
};
const handleErrors = (errors) => {
console.error(errors);
console.log(document.getElementsByName('hiddenInput')[0].value);
};
const options = {
hiddenInput: {
required: t('hiddenInput is required'),
},
};
.......
<form onSubmit={handleSubmit(handleFormSubmit, handleErrors)}>
<Select
options='...some options list'
onChange={(value) => setSomeVar(value)}
/>
<input
name='hiddenInput'
value={someVar}
{...register('hiddenInput', options.hiddenInput)}
/>
<button>submit</button>
</form>
UPDATED
Its because getElementsByName returns an array of elements.
You probably want
document.getElementsByName('hiddenInput')[0].value
I should add that really you should use a ref attached to the input and not access it via the base DOM API.
const hiddenRef = useRef(null)
// ...
cosnt handleSubmit =(e)=>{
console.log(hiddenRef.current.value);
}
// ...
<input
name='hiddenInput'
value={someVar}
ref={hiddenRef}
/>
However as you are using react-hook-form you need to be interacting with its state store so the library knows the value.
const {
register,
handleSubmit,
formState: { errors },
setValue
} = useForm({ mode: 'onBlur' });
// ...
<form onSubmit={handleSubmit(handleFormSubmit, handleErrors)}>
<Select
options='...some options list'
onChange={(value) => setValue('hiddenInput', value)}
/>
<input
name='hiddenInput'
{...register('hiddenInput', options.hiddenInput)}
/>
<button>submit</button>
</form>
You can remove const [someVar,setSomeVar]=useState('');
However, this hidden input is not really necessary as you mention in comments. You just need to bind the dropdown to react hook form.
// controller is imported from react hook form
<form onSubmit={handleSubmit(handleFormSubmit, handleErrors)}>
<Controller
control={control} // THIS IS FROM useForm return
name="yourDropdown"
rules={{required: true}}
render={({
field: { onChange, value, name, ref }
}) => (
<Select
options={options}
inputRef={ref}
value={options.find(c => c.value === value)}
onChange={val => onChange(val.value)}
/>
)}
/>
<button>submit</button>
</form>

Implement a clear button for a custom input field to trigger required error with react hook form

Expectation
React Hook form should show the error message when we clear the input field with a cross button
Issues
Required error message not shown after the value is cleared with the cross button.
After clearing value with cross button submit button is not disabled.
Code for the Custom Input Field
import React, { useRef } from 'react';
export default function MyInput(props) {
const inputRef = useRef();
const clear = () => {
if (inputRef.current) {
inputRef.current.value = '';
}
}
return (
<>
<input ref={inputRef} {...props} />
{/* I want to trigger the required error of react hook form on clear*/}
<button onClick={clear} style={{
marginLeft: '-1.2rem',
cursor: 'pointer'
}}>x</button>
</>
);
}
Usage in the form
<Controller
name="firstName"
control={control}
rules={{
required: {
value: true,
message: 'You must enter your first name'
}
}}
render={({ field: { ref, ...rest } }) => <CustomInput {...rest} />}
/>
Not sure if useRef is the right way to go, but I want to use an uncontrolled input that I want to customize with a clear button
Link to Stackblitz - Custom Input with clear Button
One way to let the form know about the change on click of the clear button is to call the setValue method from the useForm hook to register the change manually.
So, I can pass setValue as a prop to my child component i.e. the Custom Input
and set the new value on the click event of the clear button of the input field
const clear = () => {
setValue(name, '', {
shouldValidate: true,
shouldDirty: true
});
}
Also useRef is not required for this use case.
Link to Updated Stackblitz - Custom Input with clear Button

Set checkbox checked state of checkbox group programmatically in Formik

I have a checkbox group (checkboxes with the same name), that I'd like to toggle them through an external button.
The parent component will map over the data and create multiple checkboxes given the data, like so:
(options.map(
option => (
<Field
name={field.name}
value={option.value}
component={CheckBoxButton}
/>
)
)}
And the CheckBoxButton is
const CheckBoxButton = ({
value,
field,
...props
}) => {
const [activeState, setActiveState] = useState(false)
return (
<div>
<Field
type="checkbox"
{...field}
{...props}
value={value}
checked={activeState}
/>
<Button
active={activeState}
onClick={() => {
setActiveState(!activeState)
}}
>
Toggle Checkbox
</Button>
</div>
)
}
Now on submission, it seems the checked={activeState} value passed to the field seemed to be ignored, nor that it triggers onChange when changed. I have tried to use setFieldValue() but since we have multiple checkboxes with the same name, it only captures the last value. To use it means I'd have to manually structure the value array and pop/add values, which seems like an overkill.
I also tried using useRef and setting ref.current.checked manually, but at no use.
Any insights on what I might be doing wrong, or how to properly set the value programmatically?
So, I managed to get a workaround that solves the problem by wrapping the button (now a div) and checkbox within the same Label, and overriding the state within the onChange event of the field. Each field would have a unique identifier id which the label binds to using htmlFor.
While a valid workaround, it would still be nice to have a programmatic way to do so.
const CheckBoxButton = ({value,field,...props}) => {
const [activeState, setActiveState] = useState(active)
const { handleChange } = useFormikContext()
const fieldId = `${field.name}-${value}`
return (
<label htmlFor={fieldId}>
<Field
id={fieldId}
type="checkbox"
{...field}
{...props}
value={value}
onChange={(event) => {
setActiveState(!activeState)
handleChange(event)
}}
className={style.checkbox}
/>
<span active={activeState} >
Toggle Checkbox
</span>
</label>
)
}

How to reset/clear a Formik FieldArray that is conditionally rendered?

Using Formik, I am conditionally rendering via a select list value, a FieldArray that has two fields, i.e. name and age.
My question is, assuming that I have rendered this FieldArray relating to name and age and have created 5 rows of info but I then decide to change the select list value to another value that now hides this FieldArray. In doing so, how can I clear/reset this FieldArray back to null?
There can be multiple ways to do this based on the structure of your form component.
Here is one way which uses setFieldValue function exported by Formik to explicitly set a value to a field.
const FormComp = () => {
return (
<div>
<Formik>
{({ values, setFieldValue }) => (
<Form>
<select
onChange={(e) => {
const {value} = e.target;
let shouldResetFieldArray = value !== 'showFieldArray'; // replace with actual condition
if (shouldResetFieldArray) {
setFieldValue("fieldArrayKey", []); // reset fieldArrayKey to empty array [];
}
}}
></select>
</Form>
)}
</Formik>
</div>
);
};

Reset selected values from react-select within react-hook-form on submit?

I'm in the process of replacing some select inputs in my app to use react-select. Prior to using react-select, I was clearing the selected options in the form on submit using the .reset() function, which does not appear to cause any effect now.
I tried to store a variable in the onSubmit function with null as the value to be set to the defaultValue prop in the Controller thinking it would reset things. However that didn't work. What would be a clean way to handle this?
Parent form component:
function AddActivityForm(props) {
const { register, handleSubmit, control, watch, errors } = useForm();
const onSubmit = (data) => {
//Additional logic here
document.getElementById("activity-entry-form").reset();
};
return (
<Form id="activity-entry-form" onSubmit={handleSubmit(onSubmit)}>
<ActivityPredecessorInput
activities={props.activities}
formCont={control}
/>
<Button variant="primary" type="submit" className="mb-2">
Submit
</Button>
</Form>
);
}
export default AddActivityForm;
Child using Select from react-select:
function ActivityPredecessorInput(props) {
const activities = props.activities;
const options = activities.map((activity, index) => ({
key: index,
value: activity.itemname,
label: activity.itemname,
}));
return (
<Form.Group controlId="" className="mx-2">
<Form.Label>Activities Before</Form.Label>
<Controller
as={
<Select
isMulti
options={options}
className="basic-multi-select"
classNamePrefix="select"
/>
}
control={props.formCont}
name="activitiesbefore"
defaultValue={""}
/>
</Form.Group>
);
}
export default ActivityPredecessorInput;
Answering my own question based on the example provided by #Bill in the comments in the original post. React-hook-form provides a reset method that takes care of the situation pretty simply.
Create a const to store default values (stored in the parent component in this case).
const defaultValues = {
activitiesbefore: "",
};
Include reset method in the useForm const.
const { register, handleSubmit, reset, control, watch, errors } = useForm();
Call the reset method in the onSubmit function passing defaultValues.
reset(defaultValues);
You can pass the onClick
onClick={()=> setValue("activitiesbefore", "")}
parameter with the setValue value from useForm
const { register, handleSubmit, errors, reset, control, setValue} = useForm();
to the reset button, or to the submit button

Resources