Antd RadioGroup onChange fires before radio value is set - reactjs

I'm using ant design and I'm having trouble with a radio group. It seems to me like the onChange handler on the RadioGroup fires when a radio button is clicked, but before the value of that radio button is set.
Here is some sample code
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
console.log('field value', getFieldValue(name)); // previous value, not changed value.
};
const component =
getFieldDecorator &&
getFieldDecorator(name, {
initialValue: value,
rules: formatValidations(validations)
})(
<RadioGroup name={name} onChange={handleChange}>
<Radio value={1} key={1}>1</Radio>
<Radio value={2} key={2}>2</Radio>
<Radio value={3} key={3}>3</Radio>
</RadioGroup>
);
When this handleChange function runs, the field value is wrong. The first time I click radio 1, the value is undefined. Then I click 2, the value is 1. In other words, the onChange event is triggered, but the the value of the radio button is not yet set, I am only able to get the previously set value. Can anyone see anything wrong with my implementation?
Also, e.target.value is the expected changed value (it does not match the getFieldValue value).
Thanks

The antd support recommends using onValuesChange in Form.create() https://github.com/ant-design/ant-design/issues/20418
I found this workaround that works for me:
<Radio.Group
onChange={(e: RadioChangeEvent) => {
form.setFieldsValue({ yourRadioFieldNameFromGetFieldDecorator: e.target.value }, () => {
handleChange();
});
}}
>
The callback ensures that field value is set.

You should really use e.target.value if you want the value immediately. since the value is not set yet (to the antd form yet) at the moment you click the radio.(the field needs to pass setStatus, setfeedback etc)
if you really want to use getFieldValue, you can do the setTime strick
setTimeout(() => console.log(this.props.form.getFieldValue('radio-group')), 0)

Related

Unchecking a checkbox in react from several checkbox groups (I'm using React hooks)

I have several checkboxes running in several checkbox groups. I can't figure out how to uncheck (thus changing the state) on a particular checkbox. FOr some reason I can't reach e.target.checked.
<Checkbox
size="small"
name={item}
value={item}
checked={checkboxvalues.includes(item)}
onChange={(e) => handleOnChange(e)}
/>
and my function
const handleOnChange = (e) => {
const check = e.target.checked;
setChecked(!check);
};
I made a working sample of the component in this sandbox.
You need to create handleOnChange function specific to each group. I have created one for Genre checkbox group in similar way you can create for other groups.
Here is handler function.
const handleOnChangeGenre = (e) => {
let newValArr = [];
if (e.target.checked) {
newValArr = [...state.pillarGenre.split(","), e.target.value];
} else {
newValArr = state.pillarGenre
.split(",")
.filter((theGenre) => theGenre.trim() !== e.target.value);
}
setState({ ...state, pillarGenre: newValArr.join(",") });
};
pass this function as handleOnChange prop to CustomCheckboxGroup as below.
<CustomCheckboxGroup
checkboxdata={genres}
checkboxvalues={state.pillarGenre}
value={state.pillarGenre}
sectionlabel="Genre"
onToggleChange={handleGenreSwitch}
togglechecked={genreswitch}
handleOnChange={handleOnChangeGenre}
/>
comment your handleOnChange function for testing.
check complete working solution here in sandbox -
complete code
Here's how I'd do it: https://codesandbox.io/s/elastic-pateu-flwqvp?file=/components/Selectors.js
I've abstracted the selection logic into a useSelection() custom hook, which means current selection is to be found in store[key].selected, where key can be any of selectors's keys.
items, selected, setSelected and sectionLabel from each useSelection() call are stored into store[key] and spread onto a <CustomCheckboxGroup /> component.
The relevant bit is the handleCheck function inside that component, which sets the new selection based on the previous selection's value: if the current item is contained in the previous selected value, it gets removed. Otherwise, it gets added.
A more verbose explanation (the why)
Looking closer at your code, it appears you're confused about how the checkbox components function in React.
The checked property of the input is controlled by a state boolean. Generic example:
const Checkbox = ({ label }) => {
const [checked, setChecked] = useState(false)
return (
<label>
<input
type="checkbox"
checked={checked}
onChange={() => setChecked(!checked)}
/>
<span>{label}</span>
</label>
)
}
On every render, the checked value of the <input /> is set according to current value of checked state. When the input's checked changes (on user interaction) the state doesn't update automatically. But the onChange event is triggered and we use it to update the state to the negative value of the state's previous value.
When dealing with a <CheckboxList /> component, we can't serve a single boolean to control all checkboxes, we need one boolean for each of the checkboxes being rendered. So we create a selected array and set the checked value of each <input /> to the value of selected.includes(item) (which returns a boolean).
For this to work, we need to update the value of selected array in every onChange event. We check if the item is contained in the previous version of selected. If it's there, we filter it out. If not, we add it:
const CheckboxList = ({ items }) => {
const [selected, setSelected] = useState([])
const onChecked = (item) =>
setSelected((prev) =>
prev.includes(item)
? prev.filter((val) => val !== item)
: [...prev, item]
)
return items.map((item) => (
<label key={item}>
<input
type="checkbox"
checked={selected.includes(item)}
onChange={() => onChecked(item)}
/>
<span>{item}</span>
</label>
))
}
Hope that clears things up a bit.
The best way to do it, it's to save selected checkboxes into a state array, so to check or uncheck it you just filter this state array based on checkbox value property that need to be unique.
Try to use array.some() on checkbox property checked. To remove it it's just filter the checkboxes setted up in the state array that are different from that single checkbox value.

react MUI TextField inside react-hook-form Controller inside MUI Stepper-Dialog setValue not working

I have a Button that opens a MUI Dialog.
Inside the Dialog I have a MUI Stepper. My Form is split up into different parts. Some Inputs are required others are not.
//Example Input
<Controller
name="stateName"
control={control}
rules={{ required: true }}
render={({ field: { onChange, value } }) => (
<TextField
required
label="stateName"
variant="standard"
onChange={onChange}
value={value}
fullWidth
error={errors.stateName ? true : false}
helperText={errors.stateName ? "Pflichtfeld" : null}
/>
)}
/>
Full Example: https://codesandbox.io/s/gracious-tdd-dkzoqy
When I submit my form I add an entry to an existing list and display it alongside with an edit-Button.
If the edit-Button gets pressed I want to open the Dialog and have the Inputs filled with the values of the edited data.
I tried using react-hook-form setValue("field", value) but it is not working.
I also tried to pass the edit-object via Props to the nested form-steps and use setValue inside these components useEffect utilizing useFormContext() but it didn't work either.
How can I pass the values to the Inputs so they get correctly displayed in the Multi-Step-Form-Dialog?
Working CSB -> https://codesandbox.io/s/eloquent-chaum-znt71c?file=/src/App.tsx
In editHandler, state is a json string, so the simplest fix is to parse it into the object
const editHandler = (stateJSON: any) => {
const state = JSON.parse(stateJSON)
methods.reset(state);
But in submitHandler data is stringified, the submitHanlder should look smth like this:
const submitHandler = (data: any) => {
setContent(prevContent => [...prevContent,data] );
methods.reset();
setEditState(undefined);
setOpen(false);
};
Also checkout this out https://beta.reactjs.org/learn/updating-objects-in-state
and
how to avoid mutations https://www.educative.io/courses/simplifying-javascript-handy-guide/B6yY3r7vEDJ

Show hide form fields in react using onFocus and onBlur

I'm working on a form that initially only shows one input field and when it is focused, it shows other inputs and the submit button.
I also want to hide all those extra fields if the form loses focus while they are empty. And this is the part that I'm not being able to implement.
This is my code: I use a controlled form and a state to handle focus.
const FoldableForm = () => {
const [formState, setFormState] = useState(defaultFormState);
const [hasFocus, setFocus] = useState(false);
const handleOnBlur = () => {
if (!formState.message.trim() && !formState.other_input.trim()) {
setFocus(false);
}
};
return (
<form
onFocus={() => setFocus(true)}
onBlur={handleOnBlur}
>
<textarea
name="message"
onChange={(e) => setFormState({ ...formState, message: e.target.value })}
/>
{hasFocus && (
<>
<input
type="text" name="other_input"
onChange={(e) => setFormState({ ...formState, message: e.target.other_input })}
/>
<button type="button">Post comment</button>
</>
)}
</form>
);
}
Currently, if I type something in the text area, setFocus(false) is never invoked, so it works as intended.
Otherwise, if I leave it empty and click on the other input field, the handleOnBlur function is called, it sets focus to false, so the form is 'minimized'.
This is expected because the blur event (from the textarea) is triggered before the focus event (from the new input field). So I tried to use setTimeout to check, after a fraction of a second if the focus event had already occurred.
To do so, I used a second state (shouldShow) that is updated in a setTimeout inside the handleOnBlue function.
setTimeout(() => {
if(!hasFocus) {
setShouldShow(false); // this should cause the form to minimize
}
}, 100);
However, according to the react lifecycle, the value of hasFocus that is passed to the setTimeout function is at the invocation time, not at execution. So setTimeout here is useless.
I also tried to use references, but I couldn't make it work.
In your case i think that the usage of the shouldShow state is redundant and you can also avoid using a timeout which may lead to bugs.
You can take advantage of the FocusEvent.relatedTarget attribute and prevent hiding the extra fields when blur from an input and focus to another happens simultaneously.
The handleOnBlur function should look like this:
const handleOnBlur = (e) => {
if (e.relatedTarget && e.relatedTarget.name === "other_input") return;
if (!formState.message.trim() && !formState.other_input.trim()) {
setFocus(false);
}
};
You can find a working example in this code sandbox.
The problem with this approach is that if you have multiple fields appearing you need to check if any of those is focused like below:
["other_input", "another_input"].includes(e.relatedTarget.name)
This behavior is because of closures in JavaScript. The value of hasFocus is not the value of the variable at the moment your callback inside setTimeout is executed. It's the value when the onBlur callback is executed.
One solution would be to use functional updates.
Define a state which holds both hasFocus and shouldShow inside:
const [state, setState] = useState({ hasFocus: false, shouldShow: false });
When you try to access the previous state using functional updates, you get the most recent value:
setTimeout(() => {
setState((state) => {
if (!state.hasFocus) {
return { ...state, shouldShow: false };
}
return state;
});
}, 100);
codesandbox
Another solution would be to debounce a function which sets the hasFocus state to false, which imo is way better.

Can I programmatically update the values of React bootstrap <Form.Control> after it's been updated?

Is it possible to update the value of a React Bootstrap <Form.Control> after it's been edited by a user?
I've created a small Bootstrap form in React with four controlled fields. Details are below. Submission works, but I'd like a way to update the values of various fields programmatically, both to reset the form and to fill in sets of common defaults.
My code, in which various buttons call setFormContents, works until a field is updated. After this, updating the value passed into the defaultValue of the <Form.Control>. This makes sense, but I'm not sure what is the preferred way to proceed.
The variables formContents and setFormContents are assorted with the parent element of <MyForm>.
Details
The state of the form and the associated updating function is provided via props
define MyForm(props)
return <Form
onSubmit={(e) => {
e.preventDefault();
onSubmit(props.formContents);
}}
>
<Form.Group className="mb-3" controlId="field1">
<Form.Label>Field 1</Form.Label>
<Form.Control
defaultValue={props.formContents.val1}
onChange={({ target: { value } }) =>
props.setFormContents({ ...props.formContents, val1: value })
}
/>
</Form.Group>
///... Other <Form.Group>s
</Form>
In my case, the update was for all fields. My solution, which is modeled on this StackOverflow response was to reset the form before updating it.
Create a state that will store reference to the DOM <form> element.
const [formDOM, setFormDOM] = useState(null);
Use the ref= property of the <Form> element to capture the DOM element. The state will have type ?HTMLFormElement
<Form
ref={(form) => props.setFormDOM(form)}
...
>
Provide this ReactDOM to elements that update the form. Reset the form before any updates.
onClick={(e) => {
if (props.formDOM != null) {
props.formDOM.reset();
}
props.setFormContents(...);
}}
You can also use useRef() to access your From.Control programmatically :
const inputRef = useRef(null);
[...]
<Form.Control
ref={inputRef}
defaultValue={props.formContents.val1}
onChange={({ target: { value } }) =>
props.setFormContents({ ...props.formContents, val1: value })
}
/>
And then access to the value :
inputRef.current.value = someValue;
In your case, in your onClick function :
onClick={(e) => {
...
inputRef.current.value = "";
}
This approach is less concise because you have to do it on every fields but allows to independently control the content of each From.Control.

Can't change value of react-select field

I am using the React-Select library for my React app select fields. I have the same form for adding and editing data. If I don't provide any value, it works just fine, but if I want to have default values when the app renders, I can't change them. They appear as selected, but if I try to select another option it just doesn't update and the value I provided in value prop remains selected.
I tried using defaultValue instead of value, but then it doesn't select anything when the app renders.
<Select
placeholder="Deposit"
options={deposits}
getOptionValue={ option =>
option["id"]
}
getOptionLabel={ option => {
return option["name"];
}}
onChange={ value => {
this.setState({ deposit_id: value.id }}
value={{
value: deposit_id || "",
name: deposit_name || ""
}}
/>
deposits is an array of objects, and it renders the list of names, but when I click on one of them, the one that I provided in value remains selected, but it should select the one that I click on... How do I change it? Is there something wrong with my onChange function? Or what is it that I'm doing wrong?
Try the following code. The value props should be a string, i.e the value from the state.
<Select
placeholder="Deposit"
options={deposits}
getOptionValue={ option =>
option["id"]
}
getOptionLabel={ option => {
return option["name"];
}}
onChange={ value => {
this.setState({ deposit_id: value.id }}
value={deposit_id}
/>

Resources