Sorry for the general title but I don't know what else to call it at this point. I have a form that uses widgets. Im using the react-jsonschema-form package. Id use the github for the project but I don't think this is bug so I want to check here first. I think it's just how to use this React thing question. Possibly Redux as well.
So...
I have these widgets for some of the form elements in my component.
dropdownWidget = (props, type, id) => {
return (
<div>
<input id={id} type="text" className="form-control" list={type} placeholder="Select one..." onChange={(event) => { props.onChange(event.target.value); this.hideResultTables(event.target.id) }} />
<datalist id={type}>
{this.props.actionsObj.filterJsonData(type, this.props.convertDropDownDataObj).map((value, index) => { return <option key={index} value={value}>{value}</option> })}
</datalist>
</div>
)
}
multiFileWidget = (props) => {
return (
<div>
<OverlayTrigger trigger={['hover', 'focus']} placement="right" overlay={fileWidgetPopup}>
<input type="file" id="multiFileName" required={props.required} onChange={(event) => { props.onChange(event.target.value); this.getFileNames(); this.hideResultTables(event.target.id) }} multiple />
</OverlayTrigger>
<textarea id="multiFileNameList" className="form-control" rows="4" style={{ marginTop: "2%" }} readOnly />
</div>
)
}
dropdownTooltipWidget = (props, type, id, tooltip) => {
return (
<div>
<OverlayTrigger trigger={['hover', 'focus']} placement="right" overlay={tooltip} hidden>
<input id={id} type="text" className="form-control" list={type} placeholder="Select one..." onChange={(event) => { props.onChange(event.target.value); this.hideResultTables(event.target.id) }} />
</OverlayTrigger>
<datalist id={type}>
{this.props.actionsObj.filterJsonData(type, this.props.convertDropDownDataObj).map((value, index) => { return <option key={index} value={value}>{value}</option> })}
</datalist>
</div>
)
}
They are stuffed in a object like so:
multiUISchema = {
file: {
'ui:widget': this.multiFileWidget,
classNames: "uiSchema",
},
convertTo: {
'ui:widget': (props) => this.dropdownWidget(props, "ConvertToTypes", "multiConvertTo"),
classNames: "uiSchema"
},
notification: {
'ui:widget': (props) => this.dropdownTooltipWidget(props, "NotificationType", "notification", websNotificationPopup),
classNames: "uiSchema"
}
}
Where things go bad!! This function is injected in the widgets onChange handler.
hideResultTables(targetId) {
debugger;
//disable Retrieve button
if (targetId === 'multiFileName' || targetId === 'multiConvertTo') {
console.log("BEFORE::", this.state.formData)
selectedFiles = [];
document.getElementById("convertResultTable").setAttribute("hidden", true);
document.getElementById("retrieveResultTable").setAttribute("hidden", true);
this.setState({ disabled: true });
}
//leave Retrieve Button enabled
else if (targetId !== 'appIDs') {
console.log("BEFORE::", this.state.formData)
selectedFiles = [];
document.getElementById("convertResultTable").setAttribute("hidden", true);
document.getElementById("retrieveResultTable").setAttribute("hidden", true);
}
}
Basically if I hit the first if statement which has the setState for disabled to True, my onChange doesn't seem to retain the selected dropdown data to the Form Data object. It's like setState for Disabled true, which is for a button unrelated to the field does something to in the reconciliation tree possibly and/or my form data never retains the selected field in the formdata object at all?? idk. The field just stays undefined in all console logs if it's a field that hits that first if statement with the setState in it. But that setState has nothing to do with the fields being selected. It's disabling a button further down the page is all. Im sure im missing some React lifecycle, reconciliation async something or other. Im thinking of just reduxing this all but this simple bool value seems like it should stay as local state to the component. Any thoughts on things I should be checking before i redux this simple bool for my form?
To Further clarify my question, why does setState in the injected function in onChange seem to disrupt the onChange value update for the field on the form in my Widget when I select a field. That's really what this is all about. We cannot figure out why this is happening.
footnote** The dropdown data in the form is redux store data. So im selecting store data in my onchange in the widgets. Not sure if that's relevant.
Related
I have a react-final-form form, which has 2 inputs. Let's call them from and to.
What I want to do is that whenever input from changes, I set a value for input to based on input from's value.
I do that in validate function because i don't know where else i can do that. And it causes re-rendering the component in a loop.
Since I change the value of to in validate, it causes validate function to run again and again and again. How can I avoid that?
The actual code is much more complex than this but this is where i run into problems.
Thanks
const validate = (v) => {
const calculateFrom = calculate(v.from);
window.setTo(calculateFrom);
};
<Form
onSubmit={onSubmit}
validate={validate}
mutators={{
setTo: (a, s, u) => {
u.changeValue(s, 'to', () => a[0]);
},
setMax: (a, s, u) => {
u.changeValue(s, 'from', () => getMaxBalance(selectedAsset1));
},
}}
subscription={{ submitting: true, pristine: true }}
render={({
form,
pristine,
invalid,
handleSubmit,
}) => {
if (!window.setTo) {
window.setTo = form.mutators.setTo;
}
return (
<form onSubmit={handleSubmit}>
<Field name="from">
{({ input, meta }) => (
<Input
type="number"
placeholder="123"
size="input-medium"
input={input}
meta={meta}
/>
)}
</Field>
<Field name="to">
{({ input, meta }) => (
<Input
type="number"
placeholder="123"
size="input-medium"
input={input}
meta={meta}
/>
)}
</Field>
/>
First of all you could use an existing decorator
https://codesandbox.io/s/oq52p6v96y
I'm not really sure why it is re-rendering infinitely. Might have something to do with reusing the function setTo that you put on the window.
If you don't want to add that mutator library I'd try the following solutions.
use parse prop on the the Field where you can get the value and compare it with the other value that you need and return exactly what is should be check attached example parse prop
Final form allows to nest Fields inside custom components so you could just handle everything there by using useForm hook
I have a form and when it is submitted I take the values with the function saveEntity.
Now in this form, I have some fields that are showed after some other values are chosen. From these fields, I don't receive any values on my saveEntity function.
export const MyFunctionName = (props) => {
// code...
const saveEntity = (event, errors, values) => {
console.log('values ', values);
// other code
}
const RuoliChoosen = ruolo => {
if (!ruolo.ruolo) {
return <div />;
}
return (
<div>
<Row>
<Col md="6">
<AvGroup>
<Label for="accessNumber">{'Access Number'}</Label>
<AvInput
id="accessNumber"
data-cy="accessNumber"
type="text"
className="form-control"
name="accessNumber"
/>
</AvGroup>
</Col>
//.....
}
return(
<AvForm model={isNew ? {} : userClientAuthorityEntity} onSubmit={saveEntity}>
<AvInput
id="user-client-authority-application"
data-cy="application"
type="select"
className="form-control"
name="applicationId"
onChange={handleChange}
required
>
<option value="" key="0">
Select Application
</option>
{applicationList
? applicationList.map(value => {
return (
<option value={value.appCod} key={value.appCod}>
{value.appDesapplicazione}
</option>
);
})
: null}
</AvInput>
// this RuoliChoosen receive "ruoli" from another function (it works)
<RuoliChoosen ruolo={ruoli} />
)}
When I submit the form, I expect to see the values in the saveEntity, in this case only values for application and accessNumber, but I receive only value for application.
How can I fix it? Thank you.
Please format your code well when coding, since normally the format keeps up with your logic. Sometimes people are picky about the format, but really what they are saying is that they are not comfortable to read your code. Trust me, you don't want to get uncomfortable reading code ;)
const RuoliChoosen = ruolo => {
This isn't a component, instead
const RuoliChoosen = ({ ruolo }) => {
Because ruolo is a prop, not the entire props
You sent ruolo as a prop where the component is called. But You sent it as an object. And then in that component where you receive this ruolo as prop it comes as an object. If you want to access it you have to destructure it. So change it.
from
const RuoliChoosen = ruolo => {
return()
}
to
const RuoliChoosen = ({ruolo}) => {
return()
}
thanks.
I have a form which has several select as Dropdown components and I want the onChange handler to handle them individually. How can I do that?
The Dropdown are dynamically generated according to the number of questions from the database, hence it'll be ridiculous to write an onChange handler for each Dropdown (e.g. 30 questions)
component
const Dropdown = props => {
const { title, id, onChange, noSelection, options, value } = props;
return (
<div className="form-group">
<label className="control-label" htmlFor={id}>
{title}
</label>
<select
className="form-control"
name={id}
id={id}
onChange={onChange}
value={value}
>
{noSelection && <option defaultValue="" />}
{options.map(option => (
<option value={option} key={option}>{option}</option>
))}
</select>
</div>
);
};
export default Dropdown;
class Form extends Component {
constructor() {
super();
this.state = {
noSelection: true,
value,
};
this.handleSelect = this.handleSelect.bind(this);
}
handleSelect(event) {
let target = event.target;
let value = target.value;
this.setState({
noSelection: !this.state.noSelection,
value,
});
}
render() {
return (
<div className="care-sub">
<Dropdown
title="Are a developer wanting to become a programmer?"
id="select-care-leaver"
onChange={this.handleSelect}
noSelection={this.state.noSelection}
options={['Yes', 'No']}
value={this.state.value}
/>
<Dropdown
title="Do you have experience with React"
id="select-care-estranged"
onChange={this.handleSelect}
noSelection={this.state.noSelection}
options={['Yes', 'No']}
value={this.state.value}
/>
</div>
);
}
}
export default Form
I expect each Dropdown to onChange individually when the user interact with them and without changing the other Dropdowns and without writing repeated handlers for each of them.
For dynamic handling of fields, I usually give the fields a name, and handle the onChange with:
this.setState({
formData: {
...this.state.formData,
[event.target.name]: event.target.value,
},
}
As long as you render the selects with those names, and assign their value from the state, all will work well.
Note - I keep it in an object, so I can just get all the form data at once with this.state.formData.
You can assign this same onChange handler to multiple fields and they'll all work. (Note, this also works for input fields, and anything that uses .value)
It is about time your Dropdown evolves from a functional component to a react component. Since you are re-using your Dropdown component you would need to create the onChange handler for it just once as a member function to Dropdown.
Then, everytime a Dropdown renders, it will use the same member function in a different this context.
Like so:
class Dropdown extends React.Component{
onChange (e) {
//do something based on props and value
}
render(){
const { title, id, noSelection, options, value } = this.props;
return (
<div className="form-group">
<label className="control-label" htmlFor={id}>
{title}
</label>
<select
className="form-control"
name={id}
id={id}
onChange={this.onChange} // use the member function here
value={value}
>
{noSelection && <option defaultValue="" />}
{options.map(option => (
<option value={option} key={option}>{option}</option>
))}
</select>
</div>
);
}
};
export default Dropdown;
i am developing a form in reactjs using formik plugin plugin link. when i submit form i am getting text fields values but dropdown values are coming empty...
this is my dropdown select
<div className="form-group">
<Field component="select" id="category" name="category" value={this.state.value} className={"form-control"} onChange={ this.handleChange }>
<option value="lokaler">Lokaler</option>
<option value="jobb">Jobb</option>
<option value="saker-ting">Saker & ting</option>
<option value="evenemang">Evenemang</option>
</Field>
</div>
handle submit function
export default withFormik({
enableReinitialize: true,
mapPropsToValues({ category }) {
return {
category: category || ''
}
},
handleSubmit(values, { setStatus, setErrors }){
console.log("data is this: ");
console.log(values); //here i am getting all form fields values except select value returning empty value
console.log("category: "+values.category);// here i always get empty value but not getting selected value
}
i am getting all text fields values in handle submit function but i am not able to get dropdown selected value....kindly tell me how to get dropdown select value in handle submit function ?
I also faced this problem yesterday. My problem was to submit form that contains ant design dropdown. I finally make it work after hours of:
revisiting the Formik Docs
watch Andrew Mead's video Better React Form with Formik again.
also viewing Jared Palmer's Working with 3rd-party inputs #1: react-select
The doc said you need to set onChange, onBlur events to setFieldValue, setFieldTouched props respectively like 3rd-party input example:
<MySelect
value={values.topics}
onChange={setFieldValue}
onBlur={setFieldTouched}
error={errors.topics}
touched={touched.topics}
/>
So to my problem, I need to change a bit:
<Select
{...field}
onChange={(value) => setFieldValue('fruitName', value)}
onBlur={()=> setFieldTouched('fruitName', true)}
value={values.fruitName}
...
>
...
</Select>
Hope this will help.
Here is my CodeSandbox
A more robust way to handle select components whilst using Formik would be to also use Jed Watsons react-select component. The two work together nicely and abstract away a lot of the boilerplate you would normally need to implement to get the component working seamlessly with Formik.
I have forked a simple example from Jared Palmer's react-select / Formik example on codesandbox and adjusted it to reflect your code above.
import "./formik-demo.css";
import React from "react";
import { render } from "react-dom";
import { withFormik } from "formik";
import Select from "react-select";
import "react-select/dist/react-select.css";
const options = [
{ value: "lokaler", label: "Lokaler" },
{ value: "jobb", label: "Jobb" },
{ value: "saker-ting", label: "Saker & ting" },
{ value: "evenemang", label: "Evenemang" }
];
const MyForm = props => {
const {
values,
handleChange,
handleBlur,
handleSubmit,
setFieldValue
} = props;
return (
<form onSubmit={handleSubmit}>
<label htmlFor="myText" style={{ display: "block" }}>
My Text Field
</label>
<input
id="myText"
placeholder="Enter some text"
value={values.myText}
onChange={handleChange}
onBlur={handleBlur}
/>
<MySelect value={values.option} onChange={setFieldValue} />
<button type="submit">Submit</button>
</form>
);
};
class MySelect extends React.Component {
handleChange = value => {
// this is going to call setFieldValue and manually update values.topcis
this.props.onChange("option", value);
};
render() {
return (
<div style={{ margin: "1rem 0" }}>
<label htmlFor="color">Select an Option </label>
<Select
id="color"
options={options}
onChange={this.handleChange}
value={this.props.value}
/>
</div>
);
}
}
const MyEnhancedForm = withFormik({
mapPropsToValues: props => ({
myText: "",
option: {}
}),
handleSubmit: (values, { setSubmitting }) => {
console.log(values);
}
})(MyForm);
const App = () => <MyEnhancedForm />;
render(<App />, document.getElementById("root"));
There are a few gotchas, you have to include the react select css for the component to display properly
import "react-select/dist/react-select.css";
the function mapPropsToValues option field should be initialised to an object
mapPropsToValues: props => ({
myText: "",
option: {}
Finally here is a link to the codesandbox example
I'm new to React and I've been facing a problem since few hours now. Even if I found some topics on Stackoverflow or Google that seems equivalent to my issue, I'm unable to solve it...
I'm using react-select to create a simple form. For now, I have only one multi-select input. I am able to use it as expected but when I press "Submit" I want to retrieve the values selected. I tried with refs, with onChange without success. onChange is never fired, that might be an other issue as well.
var MultiSelect = React.createClass({
onLabelClick: function (data, event) {
console.log(data, event);
},
render: function() {
var ops = []
this.props.categories.forEach(function(category) {
ops.push({ label: category.name, value: category.id });
});
return (
<div>
<Select
name = {this.props.name}
delimiter=" "
multi={true}
allowCreate={true}
placeholder = {this.props.label}
options={ops} />
</div>
);
}
});
var ProductForm = React.createClass({
submit: function () {
console.log("Categories: " + this.state.categories);
},
onCategoryChange: function(e) {
console.log("CATEGORY CHANGED !!!!!!")
this.setState({categories: e.target.value});
},
render: function () {
return (
<form onSubmit={this.submit}>
<MultiSelect label="Choose a Category" name="categories" categories={this.props.categories} onChange={this.onCategoryChange}/>
<button type="submit">Submit</button>
</form>
);
}
});
PS : data categories comes from a Rails controller.
I believe your internal Select component should receive onChange from the props provided to MultiSelect, assuming your intention is to listen to changes on the Select component.
Try something like this inside your MultiSelect's render() method:
return (
<div>
<Select
name = {this.props.name}
delimiter=" "
multi={true}
allowCreate={true}
placeholder = {this.props.label}
options={ops}
onChange={this.props.onChange} />
</div>
);
Side note, I don't think e.target.value is going to work inside onCategoryChange, since react-select doesn't send standard events.