I have a Form with a couple Form.Select attributes. My onChange() works for the <Form.Select> attributes without multiple set. However, it cannot handle selections from <Form.Select> attributes that do have multiple set.
I would like to have a single onChange function that can handle data changing for instances of Form.Select with or without the "multiple" flag set.
The Form Class:
class SomeForm extends React.Component {
handleSubmit = event => {
event.preventDefault();
this.props.onSubmit(this.state);
};
onChange = event => {
const {
target: { name, value }
} = event;
this.setState({
[name]: value
});
console.log("name: " + name)
console.log("value: " + value)
};
render() {
return (
<Form onSubmit={this.handleSubmit} onChange={this.onChange}>
<Form.Select label="Size" options={sizes} placeholder="size" name="size" />
<Form.Select
label="Bit Flags"
placeholder="Bit flags"
name="flags"
fluid
multiple
search
selection
options={bits}
/>
<Form.Button type="submit">Submit</Form.Button>
</Form>
);
}
}
The logs are never called when I select options from the Form with multiple set.
Some possible bits to populate the options prop of Form.Select:
const bits = [
{key: '1', text: "some text 1", value: "some_text_1"},
{key: '2', text: "some text 2", value: "some_text_2"},
];
How can I modify onChange() to handle both Form.Select attributes as listed above?
Please note that this question is different from question on StackOverflow which are concerned only with an onChange that can only be used for updating an array.
Multiple selects are a weird beast. This post describes how to retrieve the selected values.
If you want to define a form-level handler, you need to make an exception for multiple selects:
const onChange = (event) => {
const value = isMultipleSelect(event.target)
? getSelectedValuesFromMultipleSelect(event.target)
: event.target.value
this.setState({[event.target.name]: value})
};
const isMultipleSelect = (selectTag) => selectTag.tagName === 'SELECT' && selectTag.multiple
const getSelectedValuesFromMultipleSelect = (selectTag) => [...selectTag.options].filter(o => o.selected).map(o => o.value)
Related
I'm creating a form with a file upload with help of react-hook-form and Yup. I am trying to use the register method in my child component. When passing register as a prop (destructured in curly braces) the validation and submiting doesn't work. You can always submit the form and the submitted file object is empty.
Here's a sandbox link.
There are several of problems with your code.
1- register method returns an object with these properties:
{
onChange: function(){},
onBlur:function{},
ref: function(){}
}
when you define your input like this:
<input
{...register('photo')}
...
onChange={(event) => /*something*/}
/>
actually you are overrding the onChange method which has returned from register method and react-hook-form couldn't recognize the field change event. The solution to have your own onChange alongside with react-hook-form's onChange could be something like this:
const MyComp = ()=> {
const {onChange, ...registerParams} = register('photo');
...
return (
...
<input
{...params}
...
onChange={(event) => {
// do whatever you want
onChange(event)
}}
/>
);
}
2- When you delete the photo, you are just updating your local state, and you don't update photo field, so react-hook-form doesn't realize any change in your local state.
the problems in your ImageOne component could be solved by change it like this:
function ImageOne({ register, errors }) {
const [selectedImage, setSelectedImage] = useState(null);
const { onChange, ...params } = register("photo");
return (
...
<Button
className="delete"
onClick={() => {
setSelectedImage(null);
//Make react-hook-form aware of changing photo state
onChange({ target: { name: "photo", value: [] } });
}}
>
...
<input
//name="photo"
{...params}
type="file"
accept="image/*"
id="single"
onChange={(event) => {
setSelectedImage(event.target.files[0]);
onChange(event); // calling onChange returned from register
}}
/>
...
);
}
3- Since your input type is file so, the value of your photo field has length property that you can use it to handle your validation like this:
const schema = yup.object().shape({
photo: yup
.mixed()
.test("required", "photo is required", value => value.length > 0)
.test("fileSize", "File Size is too large", (value) => {
return value.length && value[0].size <= 5242880;
})
.test("fileType", "Unsupported File Format", (value) =>{
return value.length && ["image/jpeg", "image/png", "image/jpg"].includes(value[0].type)
}
)
});
Here is the full edited version of your file.
class MySuggest extends React.Component<Props, State> {
....
....
private handleClick = (item: string, event: SyntheticEvent<HTMLElement, Event>) => {
event.stopPropagation();
event.preventDefault();
this.props.onChange(item);
}
public render() {
const { loading, value, error} = this.props;
const { selectValue } = this.state;
const loadingIcon = loading ? <Icon icon='repeat'></Icon> : undefined;
let errorClass = error? 'error' : '';
const inputProps: Partial<IInputGroupProps> = {
type: 'search',
leftIcon: 'search',
placeholder: 'Enter at least 2 characters to search...',
rightElement: loadingIcon,
value: selectValue,
};
return (
<FormGroup>
<Suggest
disabled={false}
onItemSelect={this.handleClick}
inputProps={inputProps}
items={value}
fill={true}
inputValueRenderer={(item) => item.toString()}
openOnKeyDown={true}
noResults={'no results'}
onQueryChange={(query, event) => {
if (!event) {
this.props.fetchByUserInput(query.toUpperCase());
}
}}
scrollToActiveItem
itemRenderer={(item, { modifiers, handleClick }) => (
<MenuItem
active={modifiers.active}
onClick={() => this.handleClick(item) }
text={item}
key={item}
/>
)}
/>
</FormGroup>
);
}
}
Everything works fine, I am able to make a selection from drop-down list, however I cannot use backspace in input if I made a selection. I checked the official documentation(https://blueprintjs.com/docs/#select/suggest), it has the same issue in its example. Does anyone has the similar problems and solutions?
The reason for this is once you type something in the field, it becomes an element of the page, so once you make a selection, it assumes you highlighted an element, so will assume you are trying to send the page a command for that selection (backspace is the default page-back command for most browsers).
Solution:
Create a new dialog input every time the user makes a selection, so the user can continue to make selections and edits.
It took forever.. post my solution here:
be careful about two things:
1. query = {.....} needed to control the state of the input box
2. openOnKeyDown flag, it makes the delete not working
I had a react-select rendering a list of emails, and i need to keep the selected emails as a default option when the email is selected and saved, but the defaultValues are not working. How can i do that?
Here is my select component:
const [selectedOption, setSelectedOption] = useState("")
const makeEmailOption = item => ({
value: item.id,
label: item.ccEmail,
id: item.id,
chipLabel: item.ccEmail,
rest: item,
selected: item.selected
})
const makeEmailOptions = items => items.map(makeEmailOption)
const handleChange = (value) => {
setSelectedOption(value)
props.emails(value)
}
return (
<div>
<Select
multi={true}
name={props.name}
options={makeEmailOptions(props.ccemailfilter)}
onChange={handleChange}
value={selectedOption}
/>
</div>
)
I receive everything as props and work with that to make the options. How can i do that to make the default value if a field selected is true?
You almost have it, but in this case, you are setting the value to the selectedOption instead of setting the defaultValue. Also, you are changing the default value each time there is a change, which shouldn't be needed.
const defaultVal = {value: selectedOption, label: selectedOption};
return (
<div>
<Select
multi={true}
name={props.name}
options={makeEmailOptions(props.ccemailfilter)}
defaultValue={defaultVal}
/>
</div>
)
I came with the following solution, since my component use a function to set some variables to the select, i use a useEffect to call that with a filter right after the page render.
useEffect(() => {
handleChange(makeEmailOption(props.ccemailfilter.filter(x => x.selected)))
}, [])
const handleChange = (value) => {
setSelectedOption(value)
props.emails(value)
}
So, the handleChange are called on the onChange of the select and once after the page loads, to create a value to the select to use.
Using https://github.com/ericgio/react-bootstrap-typeahead
this.state = {
pset: 'C'
};
...
setValue = (key, value) => {
const property = { ...this.state };
property[key] = value;
this.setState(property);
};
onTypeaheadChange = (key, arrVal) => {
this.setValue(key, arrVal[0])
};
...
<Typeahead
id="1"
onInputChange={e => setValue('pset', e)}
onChange={(arrVal) => onTypeaheadChange('pset', arrVal)}
selected={[this.state.pset]}
options={['A','B']}
/>
I want to be able to have a pre-selected value, but not necessarily one of the options.
https://github.com/ericgio/react-bootstrap-typeahead/blob/master/docs/Usage.md#controlled-vs-uncontrolled
Similar to other form elements, the typeahead can be controlled or
uncontrolled. Use the selected prop to control it via the parent, or
defaultSelected to optionally set defaults and then allow the
component to control itself. Note that the selections can be
controlled, not the input value.
What does this mean? I can't do what I want? Is there any workaround?
Ant Design provides a Dynamic Form Item, by using that, I can add and remove multiple fields. But now I want do nesting in that, i.e., I want to create a questionnaire like form in which I want to add multiple questions and their respective answers.
Currently, when I am adding questions, its working correct but when I am adding answer to one question, its also adding to the all questions.
The functions of adding and removing questions and answers are as given below:
remove = k => {
const { form } = this.props;
// can use data-binding to get
const keys = form.getFieldValue("keys");
// We need at least one passenger
if (keys.length === 1) {
return;
}
// can use data-binding to set
form.setFieldsValue({
keys: keys.filter(key => key !== k)
});
};
add = () => {
const { form } = this.props;
// can use data-binding to get
const keys = form.getFieldValue("keys");
const nextKeys = keys.concat(uuid);
uuid++;
// can use data-binding to set
// important! notify form to detect changes
form.setFieldsValue({
keys: nextKeys
});
};
remove1 = k1 => {
const { form } = this.props;
// can use data-binding to get
const keys1 = form.getFieldValue("keys1");
// We need at least one passenger
if (keys1.length === 1) {
return;
}
// can use data-binding to set
form.setFieldsValue({
keys: keys1.filter(key1 => key1 !== k1)
});
};
add1 = () => {
const { form } = this.props;
// can use data-binding to get
const keys1 = form.getFieldValue("keys1");
const nextKeys1 = keys1.concat(uuid1);
uuid1++;
// can use data-binding to set
// important! notify form to detect changes
form.setFieldsValue({
keys1: nextKeys1
});
};
I have created a demo on codesandbox.io.
The problem is not in the functions but in getFieldDecorator:
<FormItem>
{getFieldDecorator(`answer[${k}]`, {
validate: [
...
]
})(<Input type="" placeholder=" Enter Answer" />)
You submit the same input value for all inputs.
Without the decorator it works fine and you can put validation to your custom function and call it
<FormItem>
<Input
type=""
placeholder=" Enter Answer"
// value={this.state.answer}
// onChange={e => this.handleChange(e)}
/>
</FormItem>
UPD: Added the full code - Sandbox try