with react final-form why is meta.touched always false with third party components? - reactjs

using final-form, i have a third party input component. i've written an adapter for it. it has a validator as well, but meta.touched is always false. i've tried propagating the onFocus event up to the input, but no luck. what am i doing wrong?
const requiredValidator = value => (value ? undefined : 'is required');
const FloatingLabelInputAdapter = ({ input, meta, ...rest }) => (
<FloatingLabelInput
{...rest}
onChange={(event) => input.onChange(event)}
onFocus={(event) => input.onFocus(event)}
errorText={meta.touched ? meta.error : ''}
/>
)
// used like this:
<Field
component={FloatingLabelInputAdapter}
label="Email"
name="email"
type="text"
validate={requiredValidator}
/>
// and here's the render() of the component
render() {
const { children, label } = this.props;
const { focussing, used } = this.state;
console.log('FloatingLabelInput.props', this.props);
return (
<Group {...this.props} >
<TextInput
focussing={focussing}
innerRef={(comp) => { this.input = comp }}
onFocus={this.onFocusHandle}
onBlur={this.onBlurHandle}
onChange={this.onChange}
type={this.props.type} />
<Label
focussing={focussing}
used={used}>
{label}
</Label>
<Bar focussing={focussing} />
</Group>
);
}
}

annnnd as usual i answer my own question.
i had to propagate the onBlur() event as well, which makes sense since touched docs say it's true only after user has entered and left focus on the input.
<FloatingLabelInput
...
onBlur={(event) => input.onBlur(event)}
/>

Related

React Formik Mui TextField with custom input loosing focus

I have a Formik form which contains a few Material UI TextField components. All of them works fine, except for one who looses focus when onChange() is executed. It has a PhoneInput (react-phone-number-input) component as a custom input.
Everything works fine (data is being saved, etc.), but I have to keep clicking on the input to continue writing on it.
I have a feeling it has something to do with the ref. When logging inputRef (PhoneInputRef, provided by TextField) it doesn't look like a normal ref.
Formik component (Parent)
{...}
<Formik
initialValues={initialValues}
validationSchema={validationSchema}
onSubmit={handleClickRegister}>
{(props) => <RegisterForm {...props} />}
</Formik>
{...}
Form (Children)
{...}
const {
values: {
firstName,
lastName,
email,
confirmEmail,
password,
confirmPassword,
phoneNumber,
},
//Formik methods passed as props
errors,
touched,
handleChange,
handleSubmit,
setFieldValue,
setFieldTouched,
} = props;
{...}
//Formik onChange handler (used by every TextField besides the one containing PhoneInput)
const change = (name, e) => {
e.persist();
handleChange(e);
setFieldTouched(name, true, false);
};
//Custom onChange handler for Formik (used by PhoneInput)
const handleOnChange = (name, value) => {
setFieldValue(name, value);
setFieldTouched(name, true, false);
};
{...}
{/*NORMAL TEXTFIELD*/}
<TextField
className={classes.inputField}
id="confirmPassword"
name="confirmPassword"
label={t("header.registerDialog.form.labelConfirmPassword")}
value={confirmPassword}
onChange={change.bind(null, "confirmPassword")}
error={touched.confirmPassword && Boolean(errors.confirmPassword)}
helperText={
touched.confirmPassword && errors.confirmPassword ? (
errors.confirmPassword
) : (
<>
<br />
</>
)
}
/>
{/*CUSTOM INPUT TEXTFIELD*/}
<TextField
className={classes.textFieldPhoneInput}
key="textFieldPhone"
ref={textFieldRef}
id="phoneNumber"
name="phoneNumber"
label={t("header.registerDialog.form.labelPhone")}
InputLabelProps={{
className: phoneNumber
? classes.inputFieldPhoneLabel
: classes.inputFieldPhoneLabelEmpty,
}}
InputProps={{
inputComponent: ({ inputRef, ...rest }) => (
<PhoneInput
{...rest}
key="phoneInput"
ref={inputRef}
international
countryCallingCodeEditable={false}
name="phoneNumber"
defaultCountry="AR"
onChange={(phone) =>
handleOnChange("phoneNumber", phone)
}
labels={phoneLanguage}
value={phoneNumber}
/>
),
}}
error={touched.phoneNumber && Boolean(errors.phoneNumber)}
helperText={
touched.phoneNumber && errors.phoneNumber ? (
errors.phoneNumber
) : (
<>
<br />
</>
)
}
/>
{...}

react-hook-form isDirty seems weird for me

Today, I started to use react-hook-form and the isDirty variable seems quite weird for me.
It is always true although only the focus was given to any input elements.
I expect isDirty should be true only when value of input element changes. Is that normal in react-hook-form?
// I had to make workaround like this. but I am not sure if this is normal for everybody.
const closeForm = () => {
const { dirtyFields } = form.formState
const isReallyDirty = Object.keys(dirtyFields).length > 0
if (isReallyDirty) {
if (window.confirm("Discard the changes?")) {
dispatch(closeItem())
}
} else {
dispatch(closeItem())
}
}
UPDATE: I think this is a bug of react-hook-form?
react-hook-form version 6.11.0
This happens only when React.forwardRef was used.
const TextareaBox = ({ ref, ...props }) => {
const { errors, name } = props
const { required, ...restProps } = props
return (
<Row>
<Label {...props} columnSize={2} />
<Col lg={10}>
<textarea id={name} {...restProps} maxLength="200" rows="3" ref={ref} />
<ErrorMessage className="errorMessage" errors={errors} name={name} as="p" />
</Col>
</Row>
)
}
const TextareaBox = React.forwardRef((props, ref) => {
const { errors, name } = props
const { required, ...restProps } = props
return (
<Row>
<Label {...props} columnSize={2} />
<Col lg={10}>
<textarea id={name} {...restProps} maxLength="200" rows="3" ref={ref} />
<ErrorMessage className="errorMessage" errors={errors} name={name} as="p" />
</Col>
</Row>
)
})
I had a similar issue and I ended up solving it by checking length of dirtyFields property of the formState.
In react hook form, you may feel that isDirty behaves more like it is isTouched. But you have to pass the defaultValue to the input field because RHF needs a value to compare against as mentioned in the official documents.
Let me know if that makes sense.

input tag loses focus after one character in react-querybuilder

I am rendering a customized react-querybuilder. Whenever I add a rule the input box is rendered with default empty value. The problem is that when I enter one character in the Input box it loses focus.
This does seem like a duplicate question. But, after trying out the solutions mentioned below -
Storing value in state.
autoFocus on input tag (this is messed it up even further!)
I am not able to figure it out.
I have added the code to stackblitz
Please find the relevant code:
const [queryOutput, setQueryOutput] = useState("");
...
<QueryBuilder
{...props}
controlElements={{
combinatorSelector: props => {
let customProps = {
...props,
value: props.rules.find(x => x.combinator) ? "or" : props.value
};
return (
<div className="combinator-wrapper">
<button className="form-control-sm btn btn-light mt-2">
{customProps.value.toUpperCase()}
</button>
</div>
);
},
addRuleAction: props => {
return (
<button
className={props.className}
title={props.title}
onClick={e => {
return props.handleOnClick(e);
}}
>
+ Add New Rule
</button>
);
},
addGroupAction: props => {
return (
<button
className={props.className}
title={props.title}
onClick={e => {
return props.handleOnClick(e);
}}
>
{props.label}
</button>
);
},
valueEditor: ({
className,
field,
operator,
inputType,
value,
handleOnChange,
level
}) => {
if (field === "enabled") {
return (
<input
className={className}
type="checkbox"
checked={value !== "" ? value : false}
onChange={e => handleOnChange(e.target.checked)}
/>
);
}
return (
<input
className={className}
value={value}
onChange={e => handleOnChange(e.target.value)}
/>
);
}
}}
onQueryChange={query => {
let customQuery = { ...query, combinator: "or" };
return setQueryOutput(
formatQuery(customQuery ? customQuery : query, "sql")
);
}}
/>
Needed to assign a reference of the valueEditor component rather than defining it inline(so that it does not create a new instance on every render).
Updated the relevant code:
const valueEditor = ({
className,
field,
operator,
inputType,
value,
handleOnChange,
level
}) => (
<input
className={className}
value={value}
onChange={e => handleOnChange(e.target.value)}
/>
);
.....
<QueryBuilder
{...props}
controlElements={{
...
valueEditor
...
}}
/>

How to set state for text box in functional component

I am working on React JS. I have one text-box component and I want to show some default value in it. After that, the user should be allowed to change the value. Now I am unable to change the value. The text box is behaving like read-only. Below is my code
const EditStyleFormComponent = ({
submitting,
invalid,
}) => (
<form className={className} onSubmit={handleSubmit}>
<h2>LSPL (Low Stock Presentation Level)</h2>
<Line />
<InputGroup>
<TextFieldWithValidation name="lsplMan" label="LSPL Manual" input={{ onChnage:'', value: 'Current' }} />
</InputGroup>
</form>
);
Below is my TextFieldWithValidation code.
export const TextFieldWithValidationComponent = ({
meta,
input,
noStyles,
...otherProps
}) => (
<TextField
state={noStyles ? textFieldStates.DEFAULT : getState(meta)}
errorMessage={meta.touched ? meta.error : null}
{...input}
{...otherProps}
/>
);
Below is my TextField code.
const TextField = ({
className,
label,
description,
state,
errorMessage,
isEditable,
spaceAtBottom, // Not used, but we don't want it in otherProps
...otherProps
}) => {
const inputId = _.uniqueId();
return (
<div className={className}>
{label &&
<label htmlFor={inputId}>{label}</label>
}
<div className="input-group" id={isEditable ? 'editable' : 'readonly'}>
<input
id={inputId}
readOnly={!isEditable}
{...otherProps}
/>
{getStatusIcon(state)}
{errorMessage &&
<Error>{errorMessage}</Error>
}
{description &&
<Description>{description}</Description>
}
</div>
</div>
);
};
Can someone help me to fix this issue? Any help would be appreciated. Thanks
You can use State Hook for manage state in functional component.
Example :
const Message = () => {
const [message, setMessage] = useState( '' );
return (
<div>
<input
type="text"
value={message}
placeholder="Enter a message"
onChange={e => setMessage(e.target.value)}
/>
<p>
<strong>{message}</strong>
</p>
</div>
);
};
Yu defined onChange as empty string in EditStyleFormComponent component. So on any change input component just do nothing.
onChange should be some function that will update value.
If you want to use functional components there are two possible solutions:
Lift state up to parent component of EditStyleFormComponent (in case parent is class based component)
Use React Hooks like so (just example!)
const EditStyleFormComponent = ({
submitting,
invalid,
}) => {
const [inputValue, setInputValue] = useState ('Current'); // default value goes here
return <form className={className} onSubmit={handleSubmit}>
<h2>LSPL (Low Stock Presentation Level)</h2>
<Line />
<InputGroup>
<TextFieldWithValidation name="lsplMan" label="LSPL Manual" input={{ onChnage: (e) => { setInputValue(e.target.value); }, value: inputValue }} />
</InputGroup>
</form>
};

How to add react-phone-number-input to -react-final-form?

I'm currently creating a form using react-final-form and trying to use react-phone-number-input with it through integration as an adapter, as displayed through this example.
I attempted to use the example to learn how it is done, but I'm not sure how to access the component and create the adapter for it properly.
import React from 'react';
import { Form, Field } from 'react-final-form';
import PhoneInput from 'react-phone-number-input';
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))
const onSubmit = async values => {
await sleep(300)
window.alert(JSON.stringify(values, 0, 2))
}
const PhoneAdapter = ({ input, meta, ...rest }) => (
<PhoneInput
{...input}
{...rest}
value={input.value}
onChange={(event, value) => input.onChange(value)}
/>
)
class ContactForm extends React.Component {
render() {
return (
<>
<Form
onSubmit={onSubmit}
initialValues={{ }}
render={({ handleSubmit, form, submitting, pristine, values }) => (
<form onSubmit={handleSubmit}>
<fieldset>
<Field component={PhoneAdapter} />
</fieldset>
<fieldset>
<button type="submit" disabled={submitting || pristine}>
Submit
</button>
</fieldset>
<pre>{JSON.stringify(values, 0, 2)}</pre>
</form>
)}
/>
</>
);
}
}
export default ContactForm;
Update: July 2019
Apparently, all you need to do is to spread the input property of Field. Works flawlessly. Learn about spreading if you're not familiar with it.
const PhoneAdapter = ({ input }) => (
<PhoneInput {...input} />
)
<Field name="phone" placeholder="Enter phone number" component={PhoneAdapter} />
I ended up experimenting with the FieldRenderProps props until it worked out. I wasn't so sure whether it would work or not, as react-phone-number-input is two elements in a component. I thought it would implement the input on only one of the elements.
By using input, I gain access to the input's props. Hence, I called upon it's value, as the default looks like so:
<PhoneInput
placeholder="Enter phone number"
value={ this.state.value } // This is what I called.
onChange={ value => this.setState({ value }) }/>
I then did the same for the onChange function prop.
const PhoneAdapter = ({ input }) => (
<PhoneInput value={input.value.value} onChange={value => input.onChange(value)} />
)
Finally, I used the component adapter like so:
<Field name="phone" placeholder="Enter phone number" component={PhoneAdapter} />

Resources