I have the following: ( http://catamphetamine.github.io/react-phone-number-input/ )
type PhoneNumberInputProps = {
dataBdd: string
disabled: boolean | undefined,
label?: string,
id: string
onChange: any
value: string
error?: any
maxLength?: number
}
const PhoneNumberInput: React.FC<PhoneNumberInputProps> = ({
dataBdd,
disabled,
id,
onChange,
label,
value,
error
}) => {
const phoneInputStyle = classNames({
'error': error,
'hide': disabled
});
const [updatedValue, setUpdatedValue] = useState(value)
useEffect(() => {
setUpdatedValue(value)
}, [value])
const DEFAULT_COUNTRY_VALUE = 'GB'
const handleOnChange = (val: string) => {
setUpdatedValue(val)
onChange(val)
}
return (
<>
<Label htmlFor={id}>{label}</Label>
<PhoneInput
data-bdd={dataBdd}
disabled={disabled}
id={id}
defaultCountry={DEFAULT_COUNTRY_VALUE}
value={updatedValue}
onChange={handleOnChange}
className={phoneInputStyle}
maxLength={15}
/>
</>
)
}
export default PhoneNumberInput;
if I change the value of a phone number the ui gets updated correctly and the value property gets the + in front of the number which is what I send to the BE.
But, if there is a default value coming from the BE and I don't change that number so it doesn't get formatted the value gets sent without the +.
Is it possible to format the value regardless of the onChange being triggered?
Format the Value before setting the state in useEffect.
import PhoneInput, { formatPhoneNumber, formatPhoneNumberIntl } from 'react-phone-number-input'
<PhoneInput
placeholder="Enter phone number"
value={value}
onChange={setValue}/>
National: {value && formatPhoneNumber(value)}
International: {value && formatPhoneNumberIntl(value)}
Related
I'm using Formik for my form with google place auto-complete, I want to render places auto-complete as a custom component in the Formik field.
form.js
<Formik initialValues={location:""}>
<Field name="location" component={PlacesAutoComplete} placeholder="enter your location"/>
{...rest of form}
</Formik>
auto-complete component
import PlacesAutocomplete , {
geocodeByAddress,
geocodeByPlaceId
} from "react-google-places-autocomplete";
export const PlacesAutoComplete = ({
field: { name, ...field }, // { name, value, onChange, onBlur }
form: { touched, errors }, // also values, setXXXX, handleXXXX, dirty, isValid, status, etc.
classes,
label,
...props
}: any) => {
const [fieldName, setFildName] = React.useState(field.name);
const [address, setAddress] = React.useState(props.value || "");
const error = errors[name];
// const touch = touched[name];
const handleSelect = () => {
// set this value to formik value
};
const handleChange = () => {
// set this value to formik value
};
const handleError = () => {
props.form.setFieldError(fieldName, error);
};
return (
<PlacesAutocomplete
value={address}
onChange={handleChange}
onSelect={handleSelect}
onError={handleError}
name={name}
placeholder={props.placeholder}
id={name}
{...props}
apiKey="Api key here"
>
{({
getInputProps,
suggestions,
getSuggestionItemProps,
loading
}: any) => (
<div>
<input
{...getInputProps({
placeholder: "Search Places ...",
className: "location-search-input form-control"
})}
/>
<div className="autocomplete-dropdown-container">
{loading && <div>Loading...</div>}
{suggestions.map((suggestion: any) => {
const className = suggestion.active
? "suggestion-item--active"
: "suggestion-item";
// inline style for demonstration purpose
const style = suggestion.active
? { backgroundColor: "#fafafa", cursor: "pointer" }
: { backgroundColor: "#ffffff", cursor: "pointer" };
return (
<div
{...getSuggestionItemProps(suggestion, {
className,
style
})}
>
<span>{suggestion.description}</span>
</div>
);
})}
</div>
</div>
)}
</PlacesAutocomplete>
);
};
How I set places auto-complete value to formik value, I'm pretty new to react and confused in handle change and on change functions. also, I found a solution in react class component here, But when converting those codes into functional components I'm stuck in Onchange and onSlecet functions
Better not write functional components as you'll get stuck with the test cases if you are writing.
OnChange is even you type anything, the value gets stored in onChange.
Abe onSelect is when you select anything
Basically on change you need to call formik's field onChange function. So in case you get an event on handleChange, just do this
const handleChange = (event) => {
// set this value to formik value
field.onChange(event.target.value)
};
or in case you get value in handleChange then do this
const handleChange = (value) => {
// set this value to formik value
field.onChange(value)
};
This will sync your formik state with autocomplete state.
Now comes the part for select. In this case also you can take the same route
const handleSelect = (value) => {
// set this value to formik value
field.onChange(value)
};
or you can use the setField function of form to update the value
const handleSelect = (value) => {
// set this value to formik value
form.setField('location',value)
};
I'm starting my adventure with react and I have a reusable component of type DateTime like that:
interface IProps
extends FieldRenderProps<Date, HTMLElement>,
FormFieldProps {}
const DateInput: React.FC<IProps> = ({
input,
width,
placeholder,
date = false,
time = false,
meta: { touched, error },
id,
...rest
}) => {
const [isOpen, setIsOpen] = useState<false | "date" | "time" | undefined>(false);
return (
<Form.Field error={touched && !!error} width={width}>
<DateTimePicker
onFocus={() => setIsOpen("date")} //here I would like it to be set false or time/date depending on props value
placeholder={placeholder}
value={input.value || null}
onChange={input.onChange}
date={date}
time={time}
{...rest}
open={isOpen}
/>
{touched && error && (
<Label basic color='red'>
{error}
</Label>
)}
</Form.Field>
)
};
I would like it to open when I click on the field. Thats why I added that:
const [isOpen, setIsOpen] = useState<false | "date" | "time" | undefined>(false);
Maybe it is not the best way to do that :/
Problem with that code is that when I select input the it is set to open but even if it looses focuse then still it is open. I would like it to be closed after sleecting a date or clicking somewhere else.
And second thing is that I would like it to open date/time depending on props set on component (date = false; time = false)
Could you please help me with that task?
Ok, so I did it this way, do you think that somehow it can be improved?
interface IProps
extends FieldRenderProps<Date, HTMLElement>,
FormFieldProps {}
const DateInput: React.FC<IProps> = ({
input,
width,
placeholder,
date = false,
time = false,
meta: { touched, error },
id,
...rest
}) => {
const [isFocused, setIsFocused] = useState<false | "date" | "time" | undefined>(false);
const setOpen = (active: boolean) => {
if(!active){
setIsFocused(false);
}
else{
if(date === true){
setIsFocused("date");
}else{
setIsFocused("time");
}
}
}
return (
<Form.Field
error={touched && !!error}
width={width}>
<DateTimePicker
onBlur={() => setOpen(false)}
onFocus={() => setOpen(true)}
placeholder={placeholder}
value={input.value || null}
onChange={input.onChange}
onSelect={() => setIsFocused(false)}
date={date}
time={time}
{...rest}
open={isFocused}
/>
{touched && error && (
<Label basic color='red'>
{error}
</Label>
)}
</Form.Field>
)
};
You need to add a function that handles outside click or date selection.
e.g.:
const closePicker = () => setIsOpen(false)
...
<DateTimePicker ...
onSelect={closePicker}
...>
to watch the outside click, you need to add listener for click events.
Have a look at this answer
I have a bootstrap input which renders a list of value onChange of the input. And if a user selects a value from the list i want to show it in my input field. But it should have a ability to type again on the input field if necessary (to select another value). I'm using react-bootstrap and my approach is as follows
searchConsumer = (e) => {
const {consumer} = this.props;
let consumerValue = e.target.value;
const data = {
"filterText": consumerValue,
"page": 1,
"maxRecords": this.state.maxRecords
}
consumer(data);
}
selectConsumer(name){
this.setState({
selectedValue:name
})
}
renderConsumerList(){
const {consumers} = this.props;
if(consumers.consumerData.length > 0) {
return consumers.consumerData.map(item =>{
return (
<div className="consumer_search_item" onClick={()=>this.selectConsumer(item.name)}>{item.name}</div>
)
})
}
}
<Form.Control type="search" onChange={(e)=>this.searchConsumer(e)} value={consumers.consumerData.length > 0 ? selectedValue : ''} className="modal_input" placeholder="Search any consumer by name" required />
<div className="consumer_result_container">{this.renderConsumerList()}</div>
I can set the value successfully if i select a value from the list. But if i want to change it i cannot change it because the value is already set in the input field and does not let me delete or edit the value. How can i fix this issue?
I think what happens is when onChange (searchConsumer) is called, the new value that is typed is not updating state:
searchConsumer = ( e ) => {
const { consumer } = this.props;
let consumerValue = e.target.value;
const data = {
"filterText": consumerValue,
"page": 1,
"maxRecords": this.state.maxRecords
}
consumer( data );
// Update state because new value is typed
this.setState({
inputValue: consumerValue
})
}
Form.Control value should reflect the state:
<Form.Control type="search" onChange={ ( e ) => this.searchConsumer( e ) } value={ consumers.consumerData.length > 0 ? this.state.inputValue : '' } />
I have a Material-Ui TextField handled with Formik.
Input value (string) is converted to number on Input Change.
My problem is that when the value number zero passes, it's considered as a false value and renders an empty string.
I want it to get 'number zero' showing in TextField.
If I removes TextField value condition ( value || ' ' ), It will give me a warning message below.
Warning: `value` prop on `input` should not be null. Consider using an empty string to clear the component or `undefined` for uncontrolled components.
How can I work around with it ?
input.js
const Input = ({
classes,
icon,
styleName,
field: { name, value, onBlur },
form: { errors, touched, setFieldValue },
...props
}) => {
const errorMessage = getIn(errors, name);
const isTouched = getIn(touched, name);
const change = (e, name, shouldValidate) => {
e.persist();
const inputValue = e.target.value;
let value;
if (inputValue !== '') {
value = isNaN(inputValue) ? inputValue : parseInt(inputValue, 10);
} else {
value = null;
}
return setFieldValue(name, value, shouldValidate);
};
return (
<TextField
name={name}
value={value || ''}
onChange={e => change(e, name, true)}
onBlur={onBlur}
{...props}
className={classes[styleName]}
helperText={isTouched && errorMessage}
error={isTouched && Boolean(errorMessage)}
InputProps={{
startAdornment: (
<InputAdornment position="start">
<Icon
name={icon}
width="30"
height="30"
viewBox="0 0 30 30"
fill="none"
/>
</InputAdornment>
),
}}
/>
);
};
I experienced a situation like this in some of our projects.
This isn't specific to Material-UI but to react.
To work around this, just set the initial value to an empty string ''.
So far we're fine with setting the value to an empty string since it's the default event.target.value of input type number when it's empty.
See: https://codesandbox.io/s/affectionate-stonebraker-cgct3
The suggested solution did't work for me.
Number 0 is falsy. so it renders an empty string.
I resolved it with this approach.
const input = value === 0 || value ? value : '';
return (
<TextField
name={name}
value={input}
...
/>
);
Im trying to create a form with React. This form uses a custom Input component I created various times. In the parent form Im trying to get a complete object with all names and all values of the form:
{inputName: value, inputName2: value2, inputName3: value3}
For this, I created a 'component updated' hook, that calls the function property onNewValue to send the new value to the parent (two way data binding):
useEffect(() => {
if (onNewValue) onNewValue({ name, value });
}, [value]);
The parent form receives the data in the handleInputChange function:
export default () => {
const [values, setValues] = useState({});
const handleInputChange = ({
name,
value
}: {
name: string;
value: string | number;
}): void => {
console.log("handleInputChange", { name, value }); // All elements are logged here successfully
setValues({ ...values, [name]: value });
};
return (
<>
<form>
<Input
name={"nombre"}
required={true}
label={"Nombre"}
maxLength={30}
onNewValue={handleInputChange}
/>
<Input
name={"apellidos"}
required={true}
label={"Apellidos"}
maxLength={60}
onNewValue={handleInputChange}
/>
<Input
name={"telefono"}
required={true}
label={"Teléfono"}
maxLength={15}
onNewValue={handleInputChange}
/>
<Input
name={"codigoPostal"}
required={true}
label={"Código Postal"}
maxLength={5}
onNewValue={handleInputChange}
type={"number"}
/>
</form>
State of values: {JSON.stringify(values)}
</>
);
};
This way all elements from all inputs should be set on init:
{"codigoPostal":"","telefono":"","apellidos":"","nombre":""}
But for some reason only the last one is being set:
{"codigoPostal":""}
You can find the bug here:
https://codesandbox.io/s/react-typescript-vx5py
Thanks!
The set state process in React is an asynchronous process. Therefore even if the function is called, values has not updated the previous state just yet.
To fix, this you can use the functional version of setState which returns the previous state as it's first argument.
setValues(values=>({ ...values, [name]: value }));
useState() doesn't merge the states unlike this.setState() in a class.
So better off separate the fields into individual states.
const [nombre, setNombre] = useState("")
const [apellidos, setApellidos] = useState("")
// and so on
UPDATE:
Given setValue() is async use previous state during init.
setValues((prevState) => ({ ...prevState, [name]: value }));
The updated and fixed code, look at:
https://codesandbox.io/s/react-typescript-mm7by
look at:
const handleInputChange = ({
name,
value
}: {
name: string;
value: string | number;
}): void => {
console.log("handleInputChange", { name, value });
setValues(prevState => ({ ...prevState, [name]: value }));
};
const [ list, setList ] = useState( [ ] );
correct:
setList ( ( list ) => [ ...list, value ] )
avoid use:
setList( [ ...list, value ] )