Update one Field based on another - javascript

As a part of my form I have a field for selecting items and a button which the client clicks in order to add the items to their list of units. I wish to show the list of units selected by the user in the units text field. How do I implement the onclick function for the button so that it takes the currently selected value for the select component before appending it to the list as presented by the textbox? I have a separate save button which will take the form data and send it to the backend when clicked.
let EditUserForm = (props) => {
const { handleSubmit, units } = props;
return (
<form onSubmit={handleSubmit}>
<Field name="units" component="input" type="text" readOnly/>
<Field name="unit" component="select" >
{
units.map(u => <option value={u} key={u}>{u}</option>)
}
</Field>
<button type="button" onClick={props.updateUserUnits}>Add unit</button>
</form>
);
};

You could do something like in the code below. This combines several redux-form concepts.
Basically you want to intercept the onChange event from the select component, so you can attach some logic to it.
In this case we'll use the change prop that is passed down by redux-form. This will allow you to change the value of another field in the form.
Combine this with the formValueSelector which allows you to get a value from a specific form field.
import React from 'react'
import { connect } from 'react-redux';
import { Field, FieldArray, formValueSelector, reduxForm } from 'redux-form'
const EditUser = (props) => {
const { change, handleSubmit, selectedUnits = [], units } = props;
const handleUnitChange = (event, value) => {
const newUnits = [ ...selectedUnits, value ];
change('units', newUnits);
}
return (
<form onSubmit={handleSubmit}>
<Field name="units" component="input" type="text" readOnly/>
<Field name="unit" component="select" onChange={handleUnitChange}>
{units.map(u => <option value={u} key={u}>{u}</option>)}
</Field>
<button type="button" onClick={props.sendData}>Add unit</button>
</form>
);
};
const form = 'editUserForm';
const EditUserForm = reduxForm({
form
})(EditUser);
const selector = formValueSelector(form);
export default connect(state => ({
selectedUnits: selector(state, 'units')
}))(EditUserForm);

Related

How to disable a button in React.js until one or several form field value changes?

I have a form within a react component:
import React, { useState, useRef, useEffect } from 'react';
import { useAuth } from './AuthContext';
export default function EditProfile() {
//refs for form fields
const nameRef = useRef();
const hometownRef = useRef();
.
.
.
//refs for other fields in the form
//state variable to store data fetched from database
const [currentUserData, setCurrentUserData] = useState({});
//context that has current user data
const { currentUser } = useAuth();
//useEffect to fetch data from database when component first mounts
useEffect(() => {
... code that fetches user data ...
setCurrentUserData(doc.data());
}, []);
function handleSubmitEditProfile(e) {
e.preventDefault();
var docRef = db.collection('users').doc(currentUser.uid);
var updateDocuments = docRef
.update({
name: nameRef.current.value,
school: schoolRef.current.value,
currentCity: currentCityRef.current.value,
hometown: hometownRef.current.value,
hobbies: hobbiesRef.current.value,
})
.then(() => {
console.log('Document successfully updated');
});
}
return(
<form onSubmit={handleSubmitEditProfile}>
<div className='row'>
<div className='label'>
<label for='name'>Name</label>
</div>
<input
type='text'
name='name'
ref={nameRef}
defaultValue={currentUserData.displayName && currentUserData.displayName}
/>
</div>
.
.
.
<div className='row'>
<div className='label'>
<label for='hometown'>Hometown</label>
</div>
<input
type='text'
name='hometown'
ref={hometownRef}
defaultValue={
currentUserData.hometown && currentUserData.hometown
}
/>
</div>
<button className='submit-btn' type='submit'>
Submit
</button>
</form>
)
}
I have populated the fields in the form from the data fetched from database. The user might not have values for these fields in the database and could be entering data for the first time to be submitted to database.
I want the submit button disabled in the case where the form fields are empty and enable the button when user enters data in the form fields. I also want to make the button disabled if the form fields are populated with data from database but none of them have been edited by the user. There could be a state variable that can be set to true if there are empty fields and that disables the button but how can I set it up to also work with the later case mentioned above?
You can add the disabled tag to the button.
<button disabled={hometownRef.length < 5} ...
Using a state variable to store true/false values for disabled attribute in the button worked for both of the scenarios. I made changes by having state variables to store the field values.
const [name, setName] = useState('');
const [hometown, setHometown] = useState('');
//state to control the disabled attribute in the button
const [disabled, setDisabled] = useState(true);
and changed the input JSX to:
<input
type='text'
name='name'
defaultValue={currentUser.displayName}
ref={nameRef}
onChange={(e) => {
setName(e.target.value);
setDisabled(false);
}}
/>
which would toggle the disabled state variable to true when the user enters some info in the fields(did the same for every input fields in the JSX).
And after the database is updated in the handleSubmitEditProfile() function, I added the line
setDisabled(true);
to toggle it back so that the button is disabled after submitting the form.

onChange input in react-hook-form

I am building webapp using React and react-hook-form library. I would like to create form where change of some field triggers some event. So I need pass custom onChange
However, since v7.0 I can't use onChange because register uses its own onChange.
import React from 'react'
import { useForm } from 'react-hook-form'
const MyForm = () => {
const form = useForm()
const onChangeFirst = value => console.log('First:', value)
const onChangeSecond = value => console.log('Second:', value)
return (
<form onSubmit={form.handleSubmit(vals => null)}>
<input {...register('first')} />
<input {...register('second')} />
</form>
)
}
How can I pass onChangeFirst to first input and onChangeSecond to second input?
There are two ways to trigger onChange while input change.
1/ With Controller component (recommend)
const onChangeFirst = value => console.log('First:', value)
<Controller
control={control}
name="first"
render={({field}) => (
<input {...field} onChange={e => {
onChangeFirst(field.value);
field.onChange(e);
}} />
)}
/>
2/ With useWatch hook
const second = useWatch({
control,
name: 'second'
});
useEffect(() => {
console.log('Second:', second)
}, [second])

Is there a way to get a React component's internal values when I click a button on the parent?

Suppose I have a component like this -
const MyForm = ({ formId }) => (
<div>
<input type="text" placeholder="Full name"></input>
<input type="text" placeholder="Email"></input>
</div>
)
export default MyForm;
And then I have my App.js like so -
import React from "react";
import MyForm from "./MyForm";
const App = () => (
<div id="app">
<MyForm formId="formOne"></MyForm>
<MyForm formId="formTwo"></MyForm>
<button onClick={
() => {
// Here, when the user clicks the button,
// I want to get values of both the textboxes,
// from both the component instances
}
}>Submit</button>
</div>
)
export default App;
So basically, what I want is - when the button is clicked, I want to be able to retrieve the values of the textboxes. One way to do this is to raise an event from inside MyForm.js so that every text change is bubbled up to the parent via a callback function prop, but that feels too cumbersome, especially if the form has a lot of fields. Is there any simple or direct way to do this? Do I need to involve global state management tools like Redux?
State inside a component is specific only to that component, the parent , children or sibling of a component have no idea of the state. The only way to communicate the value from one component to another component is via props . In your case, what we need is a state to reside at the App which can then be passed as a prop to both the MyForm Components.
App.js
const [ formState, setFormState ] = useState({ formOne: {fullName: '', Email: ''}, formTwo: '' })
const updateFormValues = (formId, key, value) => {
const stateCopy = JSON.parse(JSON.stringify(formState));
const formToUpdate = stateCopy[formId];
formToUpdate[key] = value;
setFormState(stateCopy)
}
<MyForm formId="formOne" values={formState.formOne} updateFormValues={updateFormValues}></MyForm>
<MyForm formId="formTwo" values={formState.formTwo} updateFormValues={updateFormValues}></MyForm>
MyForm.js
const MyForm = ({ formId, values, updateFormValues }) => {
const onInputChange = (e, key) => {
updateFormValues(formId, key, e.target.value)
}
return(
<div>
<input type="text" onChange={(e) => onInputChange(e, 'fullName'} value={values.fullName} placeholder="Full name"></input>
<input type="text" onChange={(e) => onInputChange(e, 'email'} value={values.email} placeholder="Email"></input>
</div>
)}
export default MyForm;
To have access to data inside children components you need to lift the state to the parent component.
One-way data flow
Identify every component that renders something based on that state.
Find a common owner component (a single component above all the components that need the state in the hierarchy).
Either the common owner or another component higher up in the hierarchy should own the state.
If you can’t find a component where it makes sense to own the state, create a new component solely for holding the state and add it somewhere in the hierarchy above the common owner component.
One way to do this:
import React, { useState } from "react";
function MyForm(props) {
const { handleChange, values } = props;
return (
<div>
<label htmlFor="name">Your name</label>
<input
type="text"
placeholder="Full name"
onChange={handleChange}
value={values.name}
id="name"
name="name"
/>
<label htmlFor="email">Your email</label>
<input
type="email"
placeholder="Email"
onChange={handleChange}
value={values.email}
id="email"
name="email"
/>
</div>
);
}
function App() {
const [values, setValues] = useState({ name: "", email: "" });
const handleChange = (event) => {
const updatedForm = { ...values, [event.target.name]: event.target.value };
setValues(updatedForm);
};
return (
<div id="app">
<MyForm
formId="formOne"
values={values}
handleChange={handleChange}
></MyForm>
<button
onClick={() => {
console.log(values);
}}
>
Submit
</button>
</div>
);
}
export default App;

Clear datalist input onClick in React controlled component

I have a html5 input with an associated datalist inside a React controlled component. I want to clear the text when the input field is clicked or receives focus so all options are displayed for selection. I've followed Alfred's excellent answer in this question but am unable to achieve quite the same result in a React controlled component. Unfortunately, calling blur inside the onClick handler prevents my users from typing more than a single character because focus is (of course) lost.
How can I maintain the ability for users to type but clear the text and show the full set of options whenever the text box is clicked?
import React, { useState } from "react";
const MyForm = () => {
const [options, setOptions] = useState(["Apples", "Oranges", "Bananas", "Grapes"]);
const handleChange = (event) => {
event.target.blur();
};
const clear = (event) => {
event.target.value = "";
};
return (
<>
<input
type="input"
list="optionsList"
onChange={handleChange}
onFocus={clear}
placeholder="Select an option"
/>
<datalist id="optionsList">
{options.map((o) => (
<option key={o}>{o}</option>
))}
</datalist>
</>
);
};
export default MyForm;
Note that I've also tried a version of this that calls clear onClick rather than onFocus. That keeps me from needing to call blur() in handleChanges so the problem typing is solved. But, this requires that I click twice to see the full set of options because the list of options seems to be presented before the box is cleared.
Saw your comment on one of my question, so I figured I'd post it here as an answer instead.
Based on your use case, here is what I think you will need
import React, { useState } from "react";
const MyForm = () => {
const [options, setOptions] = useState(["Apples", "Oranges", "Bananas", "Grapes"]);
const handleChange = (event) => {
if (!event.nativeEvent.inputType) {
event.target.blur();
}
};
const clear = (event) => {
event.target.value = "";
};
return (
<>
<input
type="input"
list="optionsList"
onChange={handleChange}
onClick={clear}
onFocus={clear}
placeholder="Select an option"
/>
<datalist id="optionsList">
{options.map((o) => (
<option key={o}>{o}</option>
))}
</datalist>
</>
);
};
export default MyForm;
In order to prevent handleChange from blocking text input normally, you will have to check for event.nativeEvent.inputType, as onChange triggered by clicking on datalist will not have an inputType value. So in this case we will only perform the input blur when it is populated by datalist and keep the focus for any other events.
I have also added an additional onClick handler to clear the input regardless whether the input is already in focus or not.
I guess you actually want to have input value as a state, and not the options.
Therefore possible controlled component implementation should be:
const options = ["Apples", "Oranges", "Bananas", "Grapes"];
const EMPTY_INPUT = "";
const MyForm = () => {
const [value, setValue] = useState(EMPTY_INPUT);
const onFocusClear = () => {
setValue(EMPTY_INPUT);
};
const onChange = ({ target: { value } }) => {
setValue(value);
};
return (
<>
<input
value={value}
type="input"
list="optionsList"
onChange={onChange}
onFocus={onFocusClear}
placeholder="Select an option"
/>
<datalist id="optionsList">
{options.map((o) => (
<option key={o}>{o}</option>
))}
</datalist>
Value: {value}
</>
);
};
And making it an uncontrolled component is pretty simple by removing the onChange. Now you have the input value in ref.current.value (Not so useful use case, just an example).
const MyForm = () => {
const inputRef = useRef();
const onFocusClear = () => {
inputRef.current.value = ''
};
return (
<>
<input
type="input"
list="optionsList"
onFocus={onFocusClear}
placeholder="Select an option"
/>
<datalist id="optionsList">
{options.map((o) => (
<option key={o}>{o}</option>
))}
</datalist>
</>
);
};

Can't get selected value from dropdown

I have a form that contains an input and a dropdown that's filled with elements from an API. But I'm having a probem, whenever I submit the form It only passes the value from the input and not the dropdown.
It's weird because when I click inspect element on the form it show that each option has a value and a label.
My code is simple, I have a form that has an input and a dropdown, I get an int from the input and a value from the dropdown and it creates an element via POST request, but that happens in the background since I only pass the parameters here.
I am using the Redux Form library for my form controls
here's my code:
import React from 'react';
import {reduxForm, Field} from 'redux-form';
import {Input} from 'reactstrap';
import {connect} from 'react-redux';
import { renderField } from '../form';
import {ticketAdd} from "../actions/actions";
import {Message} from "./Message";
const mapDispatchToProps = {
ticketAdd
};
class AmendeForm extends React.Component {
onSubmit(values) {
const { ticketAdd, parkingId } = this.props;
return ticketAdd(parseInt(values.matricule),parkingId,parseInt(values.montant));
}
render() {
const { handleSubmit, submitting, voitureList } = this.props;
console.log(voitureList);
if (null === voitureList) {
return (<Message message="Pas de voitures"/>);
}
return (
<form onSubmit={handleSubmit(this.onSubmit.bind(this))}>
<Input type="select" name="matricule" id="exampleSelect" label="Matricule">
{
voitureList.map(voiture => {
return (
<option value={voiture.id} key={voiture.id}>{voiture.matricule}</option>
);
})
}
</Input>
<Field name="montant" type="number" label="Montant" component={renderField}/>
<button type="submit" className="btn btn-primary btn-big btn-block" disabled={submitting}>Ajouter ticket</button>
</form>
)
}
}
export default reduxForm({
form: 'AmendeForm'
})(connect(null, mapDispatchToProps)(AmendeForm))
It's because your dropdown form field isn't wrapped by redux-form <Field /> component.
You have to create a custom dropdown Component (let's name it <Dropdown />) and later pass it to the Field as follows:
<Field name='matricule' component={Dropdown} />
Keep in mind that in your <Dropdown /> component, you have to adapt the props passed down by <Field /> with your custom Dropdown props. For example, <Field /> will pass down input prop, which includes itself onChange, onBlur and other handlers, these should be passed down to your custom Dropdown too.
Here's a basic example how to create such a custom Dropdown component:
const Dropdown = ({ input, label, options }) => (
<div>
<label htmlFor={label}>{label}</label>
<select {...input}>
<option>Select</option>
{ options.map( o => (
<option key={o.id} value={o.id}>{o.label}</option>
)
)}
</select>
</div>
)
Usage
const options = [
{ id: 1, label: 'Example label 1' },
{ id: 2, label: 'Example label 2' }
]
<Field name='marticule' component={Dropdown} options={options} />
For advanced use-cases, please refer to the docs.
Also, you're using reactstrap, and here's a discussion of how to create such custom Dropdown component, which is adapted with redux-form.

Categories

Resources