I have a drop-down where I have several option, what I want to do is which ever option is selected I want to create that much of input fields
On change of select I am doing this
const Select_change = (e) => {
let val = parseInt(e.target.value);
console.log(val);
switch (val) {
case 2:
setinputHolder((inputHolder) => [...inputHolder, 2]);
case 3:
setinputHolder((inputHolder) => [...inputHolder, 3]);
case 4:
setinputHolder((inputHolder) => [...inputHolder, 4]);
console.log(inputHolder);
default:
setinputHolder((inputHolder) => [...inputHolder, val]);
}
};
<select
onChange={Select_change}
name="Select_op"
className="form-control"
ref={register({ required: 'Please select IOT hub' })}>
<option value={1}>1</option>
<option value={2}>2</option>
<option value={3}>3</option>
<option value={4}>4</option>
</select>
I have created one state initial which is set to be 1 as I want to show one input field,after this what I am doing is
const [inputHolder, setinputHolder] = useState([1]);
I am looping the input field on this basis
{inputHolder.map((li, index) => (
<div className="col-12 col-sm-12 col-md-8 col-lg-4 col-xl-4">
<div className="form-group input_form_label">
<input
type="text"
name="device_name"
id="device_name"
className="form-control"
placeholder="Device Name"
/>
<label className="common_label_form" htmlFor="device_name">
Device Name
</label>
</div>
</div>
))}
But it is not working as expected, what If I have unknown nos in my drop-down let say 100, so I can not use switch to 100, I want a dynamic solution which will work fine.
Here is my working code sandbox for better understanding
Do below changes in your code
const Select_change = (e) => {
let val = parseInt(e.target.value);
setinputHolder(val);
};
and while making input based on selected value
{[...Array(inputHolder).keys()].map(...)
so your logic will look like below code
<div className="row">
{[...Array(inputHolder).keys()].map((li, index) => (
<div className="col-12 col-sm-12 col-md-8 col-lg-4 col-xl-4">
<div className="form-group input_form_label">
<input
type="text"
name="device_name"
id="device_name"
className="form-control"
placeholder="Device Name"
/>
<label className="common_label_form" htmlFor="device_name">
Device Name
</label>
</div>
</div>
))}
</div>
https://codesandbox.io/s/boring-dew-k1chw?file=/src/App.js
Related
I appreciate anyone's response and suggestions. I have a react component that loads a form to edit a product. When the component is mounted, it will receive props from another function and I use these values within the prop as default value of the input fields in the form. I also have an input element that accepts uploading a file. When the file is selected, the component seems to be refreshing and setting other input fields to their default values. Here is my component:
<form onSubmit={handleSubmit}>
<div className="form-row">
<div className="col-md-4 mt-md-2">
<input
type="text"
className="form-control"
name="name"
placeholder="Name"
defaultValue={product!.name}
ref={register({
required: true,
pattern: /^[a-zA-Z ]*$/,
})}
/>
</div>
<div className="col-md-4 mt-2 mt-md-2">
<input
type="text"
className="form-control"
name="qty"
placeholder="Quantity"
defaultValue={product!.qty}
ref={register({ required: true, pattern: /^[0-9]+$/ })}
/>
</div>
</div>
<div className="form-row">
<div className="col-md-4 mt-2 mt-md-2">
<div className="input-group">
<div className="custom-file">
<input
type="file"
className="custom-file-input"
name="posImg"
id="img"
title="Add image to product if it is a menu item"
onChange={handleImg}
/>
<label
className="custom-file-label"
htmlFor="img"
aria-describedby="inputGroupFileAddon02"
>
{fileLabelState}
</label>
</div>
</div>
</div>
</div>
<div className="d-flex justify-content-end mt-2">
<button
type="submit"
className="btn zaad justify-content-end"
title="Submit product edit"
>
Update
</button>
</div>
</form>
The issue is when I choose the file and click on select, all the other input fields are reset to their default values. Here is my handleImg function:
const handleImg = (e: any) => {
e.preventDefault();
const file = e.target.files[0];
setFileLabelState(e.target.files[0].name);
setFile(file);
};
I have tried to work with the e.preventDefault(), but I have had no chance so far. Any suggestions on how I can go about this?
Thank you again.
EDIT:
I fixed the form handler and now here is my handleSubmit function:
const handleSubmit = (event: any) => {
event.preventDefault();
event.stopPropagation();
let editedProd = new FormData();
let isMenu: string = event.menuItemOption === "yes" ? "true" : "false";
editedProd.append("name", event.name);
editedProd.append("qty", event.qty);
editedProd.append("img", file);};
const handleSubmit = event => {
event.preventDefault();
let editedProd = new FormData();
let isMenu: string = event.menuItemOption === "yes" ? "true" : "false";
editedProd.append("name", event.name);
editedProd.append("qty", event.qty);
editedProd.append("img", file);};
}
try this answer note here i change (event: any) to event. this is working code from my project
I Am populating values of my input field from JSON data what am getting from back-end, now there is an edit button on UI by on click on that button I am enabling my input field but not able to type inside as I am setting some value
I want to write inside the input once I have made them editable.
const { register, handleSubmit, errors } = useForm();
const [disabled, setdisabled] = useState(false);
const [editBtn, seteditBtn] = useState(true);
<form onSubmit={handleSubmit(onSubmit)}>
{editBtn === true && (
<div align="right">
<button
className="btn white_color_btn"
type="button"
onClick={edit}
>
Edit
</button>
</div>
)}
{editBtn === false && (
<button className="btn white_color_btn" type="submit">
Save
</button>
)}
<div className="row">
<div className="form-group col-6 col-sm-6 col-md-6 col-lg-4 col-xl-4">
<input
type="text"
disable
id="firstName"
name="firstName"
value={dataItems.firstname}
disabled={disabled ? "" : "disabled"}
ref={register({ required: true })}
/>
{errors.firstname && (
<span className="text-danger">first name required</span>
)}
<br />
<label htmlFor="emp_designation">First name</label>
</div>
<div className="form-group col-6 col-sm-6 col-md-6 col-lg-4 col-xl-4">
<input
type="text"
disabled
id="lastname"
name="lastname"
value={dataItems.lastname}
disabled={disabled ? "" : "disabled"}
ref={register({ required: true })}
/>
{errors.lastname && (
<span className="text-danger">last name required</span>
)}
<br />
<label htmlFor="lastname">Lastname</label>
</div>
</div>
</form>
On click of edit
const edit = () => {
setdisabled(true);
};
Code sandbox
You need to make your input as a controlled component and write onChange handlers which will update the state. This will allow you to edit the input field values. Demo
const [disabled, setdisabled] = useState(false);
const [name, setName] = useState(empData.item.name) // setting default name
const [lastname, setLastname] = useState(empData.item.lastname) // default lastname
const edit = () => {
setdisabled(true);
};
return (<div className="container-fluid">
<div align="right">
<button className="btn" onClick={edit}>
Edit
</button>
</div>
<div className="row">
<div>
<input
type="text"
disable
id="item.value"
value={name}
onChange={(e) => {
setName(e.target.value)
}}
disabled={disabled ? "" : "disabled"}
/>
<br />
<label htmlFor="name">Name</label>
</div>
<div>
<input
type="text"
disabled
id={"lastname"}
value={lastname}
onChange={(e) => {
setLastname(e.target.value)
}}
disabled={disabled ? "" : "disabled"}
/>
<br />
<label htmlFor="lastname">Lastname</label>
</div>
</div>
</div>);
Your input is controlled by the value you are giving to it. ie: Its value is always for example empData.item.name.
And you are not providing a change handler to handle the change.
Try adding something like this:
function myChangeHandler(e){
setEditedValueSomeHow(e.target.value);
}
<input
// ...
onChange={myChangeHandler}
/>
Read more about uncontrolled components
PS: you should have had a warning message in your console like this one:
Edit:
You are using react-hook-form to manage your form but at the same time giving values to your inputs.
Please refer to this link to initialize your form values.
short story:
Remove value form your input.
Pass an object to useForm hook containing initial values.
const { register, handleSubmit, errors } = useForm({
defaultValues: {
firstName: "steve",
lastname: "smith"
}
});
Here is a working fork for your codesandbox
In order to make the input editable, you need to update a local state which controlls the input value. As suggested by you in the comments, you are using graphql to get the data, you can make use of useEffect to set the data in state and then on click of edit, update the localState
export default function App() {
const { register, handleSubmit, errors } = useForm();
const [disabled, setdisabled] = useState(true);
const [editBtn, seteditBtn] = useState(true);
const { loading, data } = useQuery("some qraphql query here"); // getting data from graphql
const [formData, setFormData] = useState({});
useEffect(() => {
setFormData(data);
}, [data]);
const edit = () => {
setdisabled(false);
seteditBtn(false);
};
const onSubmit = () => {
console.log(formData);
// submit data using formData state.
};
const handleChange = e => {
const name = e.target.name;
const value = e.target.value;
console.log(name, value);
setFormData(prev => ({ ...prev, [name]: value }));
};
return (
<div className="container-fluid">
<form onSubmit={handleSubmit(onSubmit)}>
{editBtn === true && (
<div align="right">
<button
className="btn white_color_btn"
type="button"
onClick={edit}
>
Edit
</button>
</div>
)}
{editBtn === false && (
<button className="btn white_color_btn" type="submit">
Save
</button>
)}
<div className="row">
<div className="form-group col-6 col-sm-6 col-md-6 col-lg-4 col-xl-4">
<input
type="text"
id="firstname"
name="firstname"
onChange={handleChange}
value={formData.firstname}
disabled={disabled}
ref={register({ required: true })}
/>
{errors.firstname && (
<span className="text-danger">first name required</span>
)}
<br />
<label htmlFor="emp_designation">First name</label>
</div>
<div className="form-group col-6 col-sm-6 col-md-6 col-lg-4 col-xl-4">
<input
type="text"
id="lastname"
name="lastname"
value={formData.lastname}
onChange={handleChange}
disabled={disabled}
ref={register({ required: true })}
/>
{errors.lastname && (
<span className="text-danger">last name required</span>
)}
<br />
<label htmlFor="lastname">Lastname</label>
</div>
</div>
</form>
</div>
);
}
Working mock demo
I want to repeat the same line of fields when i click on the plus button. I tried to implement this functionality based on an state attribute plus that changed to true when i click on the button then i check if this state attribute is true? add the Fields : null. but it doesn't work and i think i am missing some concept so please some help!
the component state:
this.state = {
plus : false
}
the plusHandler:
plus = (e)=>{
this.setState({
plus: true,
});
}
in the render:
<div className="form-row">
<div className="form-group col-md-5">
<label htmlFor="cRelation">Relation</label>
<select name="cRelation" defaultValue={''} id="cRelation" className="form-control">
<option disabled value=''> select relation</option>
{relationList.map(item => (
<option key={item} value={item}>{item}</option>
)
)}
</select>
</div>
<div className="form-group col-md-6">
<label htmlFor="withConcept">withConcept</label>
<select name="withConcept" defaultValue={''} id="withConcept" className="form-control">
<option value='' disabled> select concept</option>
{(conceptList|| []).map(item => (
<option key={item.conceptId} value={item.conceptId}>{item.conceptName}</option>
))}
</select>
</div>
<div className="form=group align-self-sm-center mt-2">
<button type="button" className="btn btn-sm btn-outline-success m-2" onClick={this.plus}>+</button>
<button type="button" className="btn btn-sm btn-outline-danger pr-2">-</button>
</div>
</div>
{this.state.plus?
<div className="form-row">
<div className="form-group col-md-5">
<label htmlFor="cRelation">Relation</label>
<select name="cRelation" defaultValue={''} id="cRelation" className="form-control">
<option disabled value=''> select relation</option>
{relationList.map(item => (
<option key={item} value={item}>{item}</option>
)
)}
</select>
</div>
<div className="form-group col-md-6">
<label htmlFor="withConcept">withConcept</label>
<select name="withConcept" defaultValue={''} id="withConcept" className="form-control">
<option value='' disabled> select concept</option>
{(conceptList|| []).map(item => (
<option key={item.conceptId} value={item.conceptId}>{item.conceptName}</option>
))}
</select>
</div>
<div className="form=group align-self-sm-center mt-2">
<button type="button" className="btn btn-sm btn-outline-success m-2" onClick={this.plus}>+</button>
<button type="button" className="btn btn-sm btn-outline-danger pr-2">-</button>
</div>
</div>
:null }
this is the output i want:
I'd think of it not as add/remove input fields, but rather as managing your form state to maintain necessary elements visibility.
As long as you're going to access values, selected in those input fields (e.g. upon form submit), instead of using boolean flag, you may need to store dynamic form rows within your state as array of following structure:
[
{rowId:..., selectedOptions:{relation:..., concept...}},
...
]
For simplicity sake, I'd also re-design your dynamic form rows as a separate component.
With that, I'd attach onClick() event handlers of add/remove buttons within row component to callbacks of parent form component that will append/remove array items within its state, thus making corresponding row components appear/disappear.
You may inquiry following live-snippet for complete demonstration of that concept:
const { useState } = React,
{ render } = ReactDOM
const relations = ['relation1', 'relation2', 'relation3'],
concepts = ['concept1', 'concept2', 'concept3']
const FormRow = ({rowId, selectedOptions, onSelect, onAdd, onRemove}) => {
const handleChange = e => onSelect(rowId, e.target.getAttribute('param'), e.target.value)
return (
<div>
<label>Relation:
<select param="relation" onChange={handleChange} value={selectedOptions.relation||''}>
<option value="" disabled>select relation</option>
{
relations.map((rel,key) => <option {...{key}} value={rel}>{rel}</option>)
}
</select>
</label>
<label>With Concept:
<select param="concept" onChange={handleChange} value={selectedOptions.concept||''}>
<option value="" disabled>select concept</option>
{
concepts.map((con,key) => <option {...{key}} value={con}>{con}</option>)
}
</select>
</label>
<button type="button" onClick={onAdd}>+</button>
<button type="button" onClick={() => onRemove(rowId)}>-</button>
</div>
)
}
const Form = () => {
const [rows, setRows] = useState([{rowId:0, selectedOptions:{}}]),
onAddRow = () => {
const maxRowId = Math.max(...rows.map(({rowId}) => rowId))
setRows([...rows, {rowId: maxRowId+1, selectedOptions:{}}])
},
onRemoveRow = id => setRows(rows.filter(({rowId}) => rowId != id)),
onSelectRow = (id, param, val) => {
const rowsCopy = [...rows],
item = rowsCopy.find(({rowId}) => rowId == id)
Object.assign(item, {selectedOptions:{...item.selectedOptions, [param]:val}})
setRows(rowsCopy)
}
return (
<form onSubmit={e => (e.preventDefault(), console.log(rows))}>
{
rows.map(({rowId, selectedOptions}, key) => (
<FormRow
{...{key, rowId, selectedOptions}}
onAdd={onAddRow}
onRemove={onRemoveRow}
onSelect={onSelectRow}
/>
))
}
<input type="submit" value="Submit" />
</form>
)
}
render (
<Form />,
document.getElementById('root')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.11.0/umd/react-dom.production.min.js"></script><div id="root"></div>
You need a list with the items to render. When the user click in plus button you need add a new element.
Example:
class App extends React.Component {
constructor(props) {
super(props);
this.state = { items: [{}], relationList: [], conceptList: [] };
}
addItem = () => {
var { items } = this.state;
items.push({});
this.setState({ items });
}
removeItem = (index) => {
var { items } = this.state;
items.splice(index, 1);
this.setState({ items });
}
render() {
var { items, conceptList, relationList } = this.state;
return (
<div>
{items.map((rowItem, k) => (
<div key={k} className="form-row">
<div className="form-group col-md-5">
<label htmlFor={`cRelation${k}`}>Relation</label>
<select name={`cRelation${k}`} defaultValue={''} id={`cRelation${k}`} className="form-control">
<option disabled value=''> select relation</option>
{relationList.map(item => (
<option key={item} value={item}>{item}</option>
)
)}
</select>
</div>
<div className="form-group col-md-6">
<label htmlFor={`withConcept${k}`}>withConcept</label>
<select name={`withConcept${k}`} defaultValue={''} id={`withConcept${k}`} className="form-control">
<option value='' disabled> select concept</option>
{(conceptList || []).map(item => (
<option key={item.conceptId} value={item.conceptId}>{item.conceptName}</option>
))}
</select>
</div>
<div className="form=group align-self-sm-center mt-2">
<button onClick={this.addItem} type="button" className="btn btn-sm btn-outline-success m-2">+</button>
<button onClick={() => this.removeItem(k)} type="button" className="btn btn-sm btn-outline-danger pr-2">-</button>
</div>
</div>
))}
</div>
);
}
}
Instead of Boolean , use an integer to denote the number of rows like below . plus handler will increment the count .
this.state = {
i: 1
}
Plus Handler
plus = (e) => {
this.setState({
i: this.state.i + 1
});
}
Render function :
rowfunction() {
return (<div className="form-row">
<div className="form-group col-md-5">
<label htmlFor="cRelation">Relation</label>
<select name="cRelation" defaultValue={''} id="cRelation" className="form-control">
<option disabled value=''> select relation</option>
</select>
</div>
<div className="form-group col-md-6">
<label htmlFor="withConcept">withConcept</label>
<select name="withConcept" defaultValue={''} id="withConcept" className="form-control">
<option value='' disabled> select concept</option>
</select>
</div>
<div className="form=group align-self-sm-center mt-2">
<button type="button" className="btn btn-sm btn-outline-success m-2 " onClick={this.plus}>+</button>
<button type="button" className="btn btn-sm btn-outline-danger pr-2">-</button>
</div>
</div>)
}
render() {
var rows = [];
for (let index = 0; index < this.state.i; index++) {
rows.push(this.rowfunction())
}
return rows;
}
is there a way to clear the selected value in the react select dropdown menu after choosing an option with a press of a clear button? Thank you.
import Select from 'react-select';
const TransactionDetailsPanel = props => {
const clearQuery = () => {
inputRef.current.value=null;
};
return (
<>
<div className="columns is-gapless is-marginless">
<Select className="column is-3" options={options} onChange={updateSelection}
ref={selectRef} placeholder="Advanced Detail Search" />
<input className="column is-3" type="text"
ref={inputRef} placeholder="Enter query here..."/>
<div className="buttons">
<button className="button" onClick={updateQuery}>Details Search</button>
<button className="button" onClick={clearQuery}>Clear</button>
</div>
</div>
I have a react application, where I'm using a handler the set the state, for some input forms
<div class="form-group has-danger">
<label class="form-control-label" for="inputDanger1">Username</label>
<input type="text" name="username" value={this.state.username} onChange={this.onChange} className={this.state.isUsernameCorrect ? "form-control is-valid" :"form-control is-invalid"} id="inputInvalid" />
{this.state.isUsernameCorrect ? <div class="valid-feedback">Username is acceptable!</div> :
<div class="invalid-feedback">Please enter a valid username</div> }
</div>
<div class="form-group has-danger">
<label class="form-control-label" for="inputDanger1">Password</label>
<input type="password" name="password" value={this.state.password} onChange={this.onChange} className={this.state.isUsernameCorrect ? "form-control is-valid" :"form-control is-invalid"} id="inputInvalid" />
{this.state.isPasswordCorrect ? <div class="valid-feedback">password is acceptable!</div> :
<div class="invalid-feedback">Please enter a valid password</div> }
</div>
with a respective handler
onChange = (e) =>{
const name = e.target.name;
const value = e.target.value;
this.setState(
{[name] : value
},
() => this.usernameValidator(this.state.username),
() => this.passwordValidator(this.state.password)
);
}
I want to pass two separate functions, which validates the username and password with a regex pattern. Furthermore, it controls the state, of the display messages, if the password/username is correct or not.
However I want to execute both functions (and more maybe, for adding more form data), but setSatet only accepts one callback?
setState callback accepts only one function, but inside you can pass as many functions as you like.
this.setState({ [name] : value },
() => {
this.usernameValidator(this.state.username);
this.passwordValidator(this.state.password);
}
);