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

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>
);
};

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>

useRef is getting cleared when filtering array of objects

I've created one Demo Application where I've been able to add comments while clicking on the chat icon textarea will be expanded, currently, the functionality is I've created a reference using useRef of that particular text area using unique id, and I'm saving comments to that reference array, & rendering on UI using ref.current Method, everything is working as I expected but when I click on those filter buttons, the reference is getting null! my requirement is even though I do filter comments should be persisted!
Any suggestion, Any new Approach except using useRef would be Appreciated! Thanksyou!!
Here's my codesandbox link
https://codesandbox.io/s/proud-resonance-iryir7?file=/src/App.js
Your comment ref should only contains comments, not includes textarea element. So you should create a component to handle textarea value
const TextArea = ({ value, handleSaveComment }) => {
const ref = useRef(null);
return (
<>
<textarea
placeholder="Enter Here"
ref={ref}
defaultValue={value}
></textarea>
<div
className="save-button"
onClick={() => {
handleSaveComment(ref.current.value);
}}
>
Save
</div>
</>
);
};
and use it in table
const handleSaveComment = (fsValidationId, value) => {
comment.current[fsValidationId] = value;
setExpandedId((prev) => (prev === fsValidationId ? "0" : fsValidationId));
};
<AccordionDetails>
<TextArea
handleSaveComment={(value) =>
handleSaveComment(row.id, value)
}
value={comment.current[row.id]}
/>
</AccordionDetails>
You can check full code in my codesandbox. Hope it help!

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

ReactJS: add a new input field on an option select

I'm using react-select with isMulti. I am trying to add a new input field when user selects an option from dropdown. I have somehow achieved it. I am facing a problem, let's say if I select 1st option from dropdown then it adds the input field for that particular option and when I try to select second option then 2 new input fields are added (1 for the previously selected option and the other for current selected option). In this way, I get 3 new input fields but I should be getting 2 input fields because I have selected only 2 options. Below is my code.
onChange = (e) => {
if(e){
e.map((item) => {
this.state.selectValue.push(item.value);
})
}
}
this.state.selectValue.map((item) => {
return(
<div className="row m-3">
<label className="col-md-4">{item}</label>
<div className="col-md-7">
<input type="text" name={item} className="form-control" />
</div>
</div>
)
})
<Select
isMulti
options={this.state.options}
onChange = {(e) => this.onChange(e)}
classNamePrefix="select"
/>
Note: I am storing the option's value as a "name" of input field to keep track of the quantity.
In the screenshot when I selected first then an input field with "first" label was displayed but when I selected "second" then 2 input fields showed up as highlighed in the image.
Updating state within a loop causes sideffects with the way the component re-renders. Try using a temporary array in your handler and then override state after your map function.
onChange = (e) => {
if(e){
let arr = []
e.map((item) => {
arr.push(item.value);
})
this.setState({selectValue: arr})
}
}
Updating state within a loop causes sideffects with the way the component re-renders. Try using a temporary array in your handler and then override state after your map function.

Formik Field Wrapper

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

Resources