Is it possible to control the uncontrollable input text? - React - javascript

I set the input texts values from state when the user select an item from SelectField dropdown menu of Material UI library but when it sets, the input texts become unchangeable and what I need is that when the
Here is the image;
(Before selecting an item from SelectField dropdown menu)
After selecting an item from dropdown;
All the filled input texts now become unchangeable, the empty ones are still changeable as they don't get the value from state.
Here are the codes;
<div className="form-group row">
<label className="col-md-2 control-label">Device ID</label>
<div className="col-md-10">
<input type="text" className="form-control" id="deviceId" value={this.state.deviceId} placeholder="Device ID" ref="_deviceId" />
</div>
</div>
My state;
state = {
deviceId: null,
};
And I set the input texts, when the user select an item from dropdown;
saveDeviceInternal(index, value) {
if (this.props.templatesList.length > 0){
let deviceId = value;
this.setState({deviceId});
}else{
let deviceId = this.refs._deviceId.value;
this.setState({deviceId});
}
}

Try to add this method to your class:
handleChange(e) {
let deviceId = e.currentTarget.id
this.setState({deviceId: e.target.value});
}
and add it to your input component:
<input type="text" className="form-control" onChange={this.handleChange.bind(this)} id="deviceId" value={this.state.deviceId} placeholder="Device ID" ref="_deviceId" />

Related

redux-form How to have initial value in the input field

I use redux-form for my form, following the example: https://redux-form.com/8.3.0/docs/api/fields.md/
So the <Fields /> is like so:
<Fields
names={['firstName', 'lastName']}
component={input}
validate={{
firstName: (value, allValues, props, name) => 'error'
}}
warn={{
lastName: (value, allValues, props) => 'warning'
}}
/>
the fields component that i render is this
const renderFields = (fields) => (
<div>
<div className="input-row">
<input {...fields.firstName.input} type="text"/>
{fields.firstName.meta.touched && fields.firstName.meta.error &&
<span className="error">{fields.firstName.meta.error}</span>}
</div>
<div className="input-row">
<input {...fields.lastName.input} type="text"/>
{fields.lastName.meta.touched && fields.lastName.meta.error &&
<span className="error">{fields.lastName.meta.error}</span>}
</div>
</div>
)
So far so good, the form displays the 2 input fields and i can add values into them.
But how do i pass default values into the input's ?
When i add the value property into the input, i cant edit the input afterwards.
For example, i add the value prop with a value like so:
const renderFields = (fields) => (
<div>
<div className="input-row">
// add value="my name"
<input {...fields.firstName.input} type="text" value="my name" />
{fields.firstName.meta.touched && fields.firstName.meta.error &&
<span className="error">{fields.firstName.meta.error}</span>}
</div>
<div className="input-row">
// add value="my last name"
<input {...fields.lastName.input} type="text" value="my last name" />
{fields.lastName.meta.touched && fields.lastName.meta.error &&
<span className="error">{fields.lastName.meta.error}</span>}
</div>
</div>
)
In that way, the inputs have always the same init value.
Any help on how to have default value and also be able to edit it, thank you.
When you provide the value prop you will need to provide onChange function as well and handle the state - https://reactjs.org/docs/forms.html#controlled-components
and from redux-form docs: https://redux-form.com/8.3.0/docs/api/field.md/#-code-onchange-event-newvalue-previousvalue-name-gt-void-code-optional-
You need a state variable to hold the input value.
const [inputValue, setInputValue] = useState('');
In the input tag, use the previously declared state variable as value & in onChange of input, set the input value to the target value.
<input type="text" value={inputValue} onChange={e => setInputValue(e.target.value)} />
You can use prop defaultValue, as mentioned in Redux Form documentation :https://redux-form.com/6.0.0-alpha.4/docs/api/field.md/#props
<Field component={your custom component} defaultValue={}/>

React Typescript: Add another row of fields to form with button

I'm building a product sales form that allows users to add a product name, quantity, and price. By default I want the form to show 3 rows and if they want to add another product, there would be a button that says "Add Product" which will then add another two of product name, quantity, and price.
I want to set the limit to allow to a max of 10 rows (10 products).
addProductRow(event) {
event.preventDefault();
}
<div className={`${styles.flexWrap} ${styles.flexRow}`}>
<div>
<label>
Product Name
</label>
<input
value={this.state.DetailedDescription1}
onChange={this.handleChangeDetailedDescription1}
type="text"
className="phone validate"
name="Detailed Description"
maxLength={45}
/>
</div>
<div>
<label>
Quanitity
</label>
<input
value={this.state.Quantity1}
onChange={this.handleChangeQuantity1}
type="number"
className="phone validate"
name="Quantity #1"
maxLength={9}
pattern='[0-9]{0,5}'
/>
</div>
<div>
<label>
Total
</label>
<input
value={this.state.Total1}
onChange={this.handleChangeTotal1}
type="number"
className="phone validate"
name="Quantity #1"
maxLength={9}
pattern='[0-9]{0,5}'
/>
</div>
</div>
<button onClick={this.addProductRow}>Add New Product</button>
<button onClick={this.removeProductRow}>X</button>
So when the user clicks "Add New Product" a new row will show with the above fields labeled this.state.DetailedDescription2 and this.handleChangeDetailedDescription1 and so forth.
you could store the row data in component state (below example uses the useState hook, but there are other approaches depending on your component structure):
const [rows, setRows] = useState([{ /* row1 data */ }, { /* row 2 data */ }, { /* row3 data */ }])
and then your addProductRow could look something like:
addProductRow(){
if (this.state.rows.length > 9) return 'you cannot add more than 10 rows'
const rows = JSON.parse(JSON.stringify(this.state.rows)) // make a deep copy since this.state.rows is an array of objects
rows.push({ /* "new row" data in here */ })
setRows(rows)
}
then render the rows by doing
this.state.rows.map((row, i) => (/* ...all your jsx for a single row goes in here. you can use the index i to reference a specific row of this.state.rows in your change handlers. */))

How do I set initial value for radio button in redux-form?

I'm using the Field-component and setting type=radio and in the component rendering the input I have set checked={props.input.value}. The group currently contains 2 radio buttons, but 1 more will soon be added. I have added initialValues: { method (name of radio button): '201' (value of the one I want checked initially) } to the reduxForm wrapper.
My problem is that when I use the settings mentioned above, the last radio button in the group gets checked initially, but I want to be able to control which radio button that will initially be checked.
Code that renders the input:
export const RadioButton = props => {
return (
<div className="booking-radio-container">
{props.meta.touched &&
((props.meta.error && <label className="errorCheck">
{props.meta.error}</label>) ||
(props.meta.warning && <label>{props.meta.warning}
</label>))}
<label className="container" onClick={() =>
props.changeFunc(props.input.value)}>
{props.label}
<input {...props.input} type={props.type} checked=
{props.input.value} />
<span className="checkmark" />
</label>
</div>
);
}
The Field:
<FormSection name="paymentMethod">
<Field
name="method"
label="Creditcard"
component={RenderRadio}
type="radio"
changeFunc={this.props.selectPaymentMethod}
icon={CreditIcon}
value="201"
/>
<Field
name="method"
label="Invoice"
component={RenderRadio}
type="radio"
changeFunc={this.props.selectPaymentMethod}
icon={InvoiceIcon}
value="101"
/>
</FormSection>
The redux-form wrapper:
reduxForm({
form: 'bookingForm1',
enableReinitialize: true,
validate: ValidateContact,
asyncValidate: AsyncValidate,
asyncBlurFields: ['ContactForm.email'],
initialValues: {
method: '201'
}
})
So, what I want is for the credit card option to be checked initially, regardless of where it is in the list of fields, but if the user clicks on another option it should be set as checked.
I have tried using defaultChecked, but if i clicked the other option it would revert to credit card.
Right now with the setup described above, I can't check the other option, but if i click the checked option it swithces to the unchecked one and the back again.

Angular 4 how to show hide nested Form Group validation

I am working on angular 4 validation. I have a reactive form that have two radio button and two form groups. If user select's first radio button, it will remove validation from second form group and add validation on first form group and when select's second radio button it will add validation on second form group and remove from first form group.
Below is my form group example
this.mainForm = this.formBuilder.group({
cardOrBank: new FormControl(''),
cardDetails: new FormGroup({
cardNo: new FormControl(''),
cvv: new FormControl('')
}),
bankDetails: new FormGroup({
accNo: new FormControl(''),
ifsc: new FormControl('')
})
});
HTML
<form [formGroup]="mainForm" (ngSubmit)="onFormSubmit()">
<div>
Select: <input type="radio" formControlName="cardOrBank"> card
<input type="radio" formControlName="cardOrBank"> bank
</div>
<div formGroupName="cardDetails">
<div>
Card No: <input formControlName="cardNo">
</div>
<div>
CVV: <input formControlName="cvv">
</div>
</div>
<div formGroupName="bankDetails">
<div>
ACC No: <input formControlName="accNo">
</div>
<div>
IFSC: <input formControlName="ifsc">
</div>
</div>
<div>
<button type="submit">Submit</button>
</div>
</form>
If select card from radio button, it will add validation on cardDetails form and remove validation from bankDetails and vice versa.
P.S: Form fields may be more according to the requirement.
Thanks.
After doing a lot of work finally i was able to achieve this.
Below are the changes you need to make in your code :
// in component.ts file :
// Write two genric methods which sets and clears the validator
setRequireValidator(form:any){
for (const field in form.controls) { // 'field' is a string
let con = form.get(field); // 'control' is a FormControl
con.setValidators([Validators.required]);
con.updateValueAndValidity();
}
}
removeValidator(form:any){
console.log('form contro',form);
for (const field in form.controls) { // 'field' is a string
let con = form.get(field); // 'control' is a FormControl
con.clearValidators();
con.updateValueAndValidity();
}
// while initiallizing the form ragister the event for value changed on `cardOrBank`
// check which value user has selected and accordingly toggle between them
this.mainForm.get('cardOrBank').valueChanges.subscribe((val) => {
const cardControl = this.mainForm.get('cardDetails');
const bankControl = this.mainForm.get('bankDetails');
if(val === 'card') {
alert('card sletecd')
this.removeValidator(bankControl);
this.setRequireValidator(cardControl);
} else{
alert('bank sletecd')
this.removeValidator(cardControl);
this.setRequireValidator(bankControl);
}
});
<!-- In component.html file-->
<form [formGroup]="mainForm" (ngSubmit)="onFormSubmit()">
<div>
<label>
<!-- You missed value attribute -->
<input type="radio" value="card" formControlName="cardOrBank">
<span>Card</span>
</label>
<label>
<input type="radio" value="bank" formControlName="cardOrBank">
<span>Bank</span>
</label>
</div>
<div formGroupName="cardDetails">
<div>
Card No: <input formControlName="cardNo">
</div>
<div>
CVV: <input formControlName="cvv">
</div>
</div>
<div formGroupName="bankDetails">
<div>
ACC No: <input formControlName="accNo">
</div>
<div>
IFSC: <input formControlName="ifsc">
</div>
</div>
<div>
<button type="submit" [disabled]="!mainForm.valid">Submit</button>
</div>
</form>
Here is the Working Example of your requirement :
Working demo
you can do like this
addValidation() {
this.form.get('title').setValidators([Validators.required, Validators.minLength(3)]);
this.form.get('title').updateValueAndValidity();
}
removeValidation() {
this.form.get('title').clearValidators();
this.form.get('title').updateValueAndValidity();
}
You can do two things, either you can put explicit method or you can subscribe to the changes happens to formControlName to perform this validator switch.
if you want to subscribe then subscribe on ngOnInit() lifecycle hook:
ngOnInit() {
this.mainForm.get('cardDetails').valueChanges.subscribe((val) => {
if(val === 'card') {
this.mainForm.get('cardDetails').setValidators(Validators.required);
} else {
this.mainForm.get('bankDetails').removeValidators(Validators.required);
}
this.mainForm.get('bankDetails').updateValueAndValidity();
});
}
**add Value attribute in the html.**
**2nd Option :**
Select: <input type="radio" formControlName="cardOrBank (change)="changedPayment('card')"> card
<input type="radio" formControlName="cardOrBank (change)="changedPayment('bank')> bank
changedPayment(val) {
if(val === 'card') {
this.mainForm.get('cardDetails').setValidators(Validators.required);
} else {
this.mainForm.get('bankDetails').removeValidators(Validators.required);
}
this.mainForm.get('bankDetails').updateValueAndValidity();
}
I assume that cardOrBank form control has two values 'card' and 'bank'. In your ngOnInit, you should subscribe the valueChanges of your radio button form control.
ngOnInit() {
this.mainForm.get('cardDetails').valueChanges.subscribe((cardOrBank) => {
if(cardOrBank === 'card') {
this.mainForm.get('cardDetails').setValidators(Validators.required);
this.mainForm.get('bankDetails').removeValidators(Validators.required);
} else {
this.mainForm.get('bankDetails').setValidators(Validators.required);
this.mainForm.get('cardDetails').removeValidators(Validators.required);
}
this.mainForm.get('bankDetails')updateValueAndValidity();
this.mainForm.get('cardDetails')updateValueAndValidity();
});
}
Your radio button should have a value attribute
<div>
Select: <input type="radio" value="card" formControlName="cardOrBank"> card
<input type="radio" value="bank" formControlName="cardOrBank"> bank
</div>

Reactjs how to manage multiple interactions between component

I struggling and a bit lost with react about one thing.
I have a row with 5 columns.
Each columns have a checkbox, an input and a button.
So if you look at it in a standard way, it looks like this:
render() {
return (
<div className="sbm-sources">
<div className="col-md-2 sbm-source">
<input type="checkbox" name="c1"/>Add to source.
<br />
<input type="text" name="i1" size="10" onClick={this.expandInput} placeholder="Enter you query." />
</div>
<div className="col-md-2 sbm-source">
<input type="checkbox" name="c2"/>Add source to blipp.
<br />
<input type="text" name="i2" size="10" onClick={this.expandInput} placeholder="Enter you query." />
</button>
</div>
<div className="col-md-2 sbm-source">
<input type="checkbox" name="c3" />Add source to blipp.
<br />
<input type="text" name="i3" size="10" onClick={this.expandInput} placeholder="Enter you query." />
</button>
</div>
<div className="col-md-2 sbm-source">
<input type="checkbox" name="c4" />Add source to blipp.
<br />
<input type="text" name="i4" size="10" onClick={this.expandInput} placeholder="Enter you query." />
</div>
<div className="col-md-2 sbm-source">
<input type="checkbox" name='c5' />Add source to blipp.
<br />
<input type="text" name="i5" size="10" onClick={this.expandInput} placeholder="Enter you query." />
</div>
</div>
);
}
};
The thing is, each column can be validated separately but I need to know which one is trigger.
I know how to do it using the name of each one, but I am not sure that creating a state for EACH input / checkbox and then check which one is triggered one for then associating the data before sending a POST request is the best option here.
ex:
handleChecked(e){
if (e.value.name === c1){
this.setState({checkedC1: true});
}
...
}
This would quickly become messy and hard to maintain, or to make adaptable.
The thing is, when I want to do my Post request, I would love to receive an int. For example, if the checkbox (and / or) input filled is from the first column, the int received would be 0.
Is there ean elegant way of doing so with react? What would you suggest?
I am too much into my code and my lack of experience make me blind about it.
Many thanks!
You would need to keep the state of all your columns inside the parent component, because from there you send your post request.
create an array of column data, and put the array in state
inside render, use .map() to loop over the array and render a Column for each item in the array
optional: put the column inside a separate (stateless) component.
Your state could be like:
// as part of your constructor
let initialColumns = [
{ checked: false, checkText: "Add to source.", inputPh="Enter your query.", value: ""},
...
{ checked: false, checkText: "Add source to blipp.", inputPh="Enter your query.", value: ""}
];
this.state = { colDataArr: initialColumns }
And in your render do:
render() {
let expandInput = this.expandInput;
<div>
{this.state.colDataArr.map( colItem, index => {
<Column
checked = {colItem.checked}
checkText = {colItem.checkText}
...
expandInput = {(e) => { expandInput(e) }} // <== special here
colID = {index} // and here
})}
</div>
}
Create a (stateless) <Column> component that takes the function expandInput as a prop, alongside the other variable props. Whenever this function is called, you get the event, but also the index of the column (from 0-4).
That way, inside expandInput, you can handle one individual update
expandInput(event, type, index) {
// create copy of column object, to avoid mutating state directly
let origColData = this.state.colDataArr[index]
let newColData = {
checked = origColData.checked,
checktext = origColData.checkText,
...
}
// now, do whatever you need with the event data to update the newColData
if (type == "checkbox") {
let checked = e.target.checked
} else {
...
}
// copy the array and replace the updated one
let newColArr = this.state.colDataArr.slice()
newColArr[index] = newColData
// set the new state
this.setState({ colDataArr : newColArr })
}
UPDATE
And your shiny new stateless component could look something like this:
class Column extends React.Component {
constructor(props) {
super(props)
}
render() {
<div className="col-md-2 sbm-source">
<input type="checkbox"
onClick={(e) => this.props.expandInput(e,"checkbox", this.props.colID)}/>
{this.props.checkText}
<br />
<input type="text" size="10"
onChange={(e) => this.props.expandInput(e,"text", this.props.colID)}
placeholder={this.props.inputPH} />
<button
onClick={(e) => this.props.expandInput(e,"button", this.props.colID)}>
Do something
</button>
</div>
}
}
Slightly easier way to maintain is to store the exact field variable name in the name instead, and do:
handleChecked(e){
this.setState({[e.value.name]: true});
...
}

Categories

Resources