Redux Form: Input stays with 'touched: false' - javascript

Wanted to validate my inputs and change the CSS depending of the user interaction.
Starting with a required validation method I wrap all my inputs component with a <Field> and pass to validate an array of func. Just required for now.
But for all my fields the value stay the same touched: false and error: "Required". If I touch or add stuff in the input, those values stay the same.
Validation
export const required = value => (value ? undefined : 'Required')
NameInput
import React from 'react';
import { Field } from 'redux-form'
import InputItem from 'Components/InputsUtils/InputItem';
import { required } from 'Components/InputsUtils/Validation';
const NameInput = () => (
<Field
name={item.spec.inputName}
type={item.spec.type}
component={InputItem}
validate={[required]}
props={item}
/>
);
export default NameInput;
InputItem
import React from 'react';
const InputItem = ({ spec, meta: { touched, error } }) => {
const { type, placeholder } = spec;
return (
<input
className="input"
type={type}
placeholder={placeholder}
/>
);
};
export default InputItem;

There are 2 solutions to solve the "touched is always false" issue.
1) Ensure that input.onBlur is called in your component
For an input:
const { input } = this.props
<input {...input} />
For custom form elements without native onBlur:
const { input: { value, onChange, onBlur } } = this.props
const className = 'checkbox' + (value ? ' checked' : '')
<div
className={className}
onClick={() => {
onChange(!value)
onBlur()
}}
/>
2) Declare your form with touchOnChange
const ReduxFormContainer = reduxForm({
form: 'myForm',
touchOnChange: true,
})(MyForm)

The redux-form controls its own props within your <input /> element as long as you use the spread operator to pass those props into your input.
For example, where you are doing const InputItem = ({ spec, meta: { touched, error } }) => ...
Try destructing the input from the Component: const InputItem = ({ input, spec, meta: { touched, error } }) => ...
And where you have your <input ... />, try doing the following:
<input
{...input}
className="input"
type={type}
placeholder={placeholder}
/>
The redux-form captures any onBlur and onChange events and uses its own methods to change the touched state. You just need to pass those along as shown above.
These are what you need: https://redux-form.com/7.1.2/docs/api/field.md/#input-props

Another point to consider:
I passed {...props} to my custom component, however, touched still remained false.
That is because although props contained the input object, my component couldn't
deduce onBlur from it. When explicitly stating <CustomComponent {...this.props} onBlur={this.props.input.onBlur}, it worked as expected.

Related

Formik: target is undefined when passing Material UI Date Picker as prop to React Component

I'm trying to create a reusable component. where I am passing the form fields as prop. when I click on datepicker field. I'm getting this error:
TypeError: target is undefined
How can I fix this?
Here's how my input component looks like:
import { useField } from 'formik';
import { DatePicker } from '#material-ui/pickers';
export const DatePickerField = ({ label, ...props }) => {
const [field, meta] = useField(props);
return (
<DatePicker label={label} fullWidth {...field} {...props} />
);
};
Here's how the reusable component looks like:
import { Form } from 'formik'
export const ReusableComp = ({ fields }) => (
<Form noValidate>
{fields}
</Form>
)
Here's where I am using this component:
export const App = () => (
<ReusableComp fields={
<div className='mb-3'>
<DateTimePickerField
label='Start DateTime'
name='start_date_time'
/>
</div>
} />
)
Result of console.log(fields)
In my case I was using the Material UI DesktopDatePicker (with Typescript), and defining the onChange prop like this solved it:
onChange={(value): void => {
formik.setFieldValue("dueDate", value);
}}
You have to replace "dueDate" with your formik value name.
After lots of research and thanks #Rosen Tsankov for pointing my attention to the onChange function.
I have seen questions about the same error on SO which are not answered. so this may help them and anyone in future facing this error.
As #Rosen Tsankov have said the material ui DatePicker component returns the date value as the first argument of the onChange function.
the field returned from useField have the following: name, value, onChange, onBlur.
the onChange function returned from useField expects the first argument to be an event which in this case is the date value. that's why we get the error:
TypeError: target is undefined
Because formik is trying to access target property and date has no property target. something like this date.target and this is undefined
so here's how I have fixed it. instead of spreading the field. I have added name and value from field to DatePicker. then I have used the setFieldValue from formik to manually update the input value like so.
import { DatePicker } from '#material-ui/pickers';
import { useField, useFormikContext } from 'formik';
export const DatePickerField = ({ label, ...props }) => {
const [field, meta] = useField(props);
const { setFieldValue } = useFormikContext();
return (
<DatePicker
fullWidth
{...props}
label={label}
name={field.name}
value={field.value}
helperText={meta.error}
error={meta.touched && Boolean(meta.error)}
onChange={(value) => setFieldValue(field.name, value)}
/>
);
};
DatePicker expects onChange((date) => ...) but you are passing formik handler wich expects onChange((event) => ....)

'Illegal invocation' using input setCustomValidity and TS

I'm pretty much new to Typescript world and am converting one of the first React applications.
Here I'm trying to set custom messages on input validation by using event.target.setCustomValidity but I'm getting the 'Illegal invocation' error when the setCustomValidity function gets triggered.
Here there is the sandbox; To trigger the error it's just required to update the first input.
Debugging the error, I'd think the issue stands on the declared type of event.target set as HTMLInputElement, or on the declared type of event set as a React.SyntheticEvent; Perhaps I'm using the wrong types for the event which doesn't always have a setCustomValidity function?
To premise the code used on the sandbox works perfectly fine without Typescript
Any suggestion is highly appreciated, thank you in advance for your help!
Components code
App.tsx:
import React, { useState } from "react";
import Input from "./Input";
import "./styles.css";
export default function App() {
// Check if value entered is a valid email. Ref: http://emailregex.com/
const emailPattern = /^(([^<>()[\]\\.,;:\s#"]+(\.[^<>()[\]\\.,;:\s#"]+)*)|(".+"))#((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))/;
const initialForm = {
username: "",
password: ""
};
const [form, setForm] = useState(initialForm);
// Listen to form inputs and updates form state respectively
const handleChange = (event: React.SyntheticEvent) => {
const {
name,
value,
type,
setCustomValidity
} = event.target as HTMLInputElement;
setForm({ ...form, [name]: value });
if (type === "email" && !emailPattern.test(value)) {
setCustomValidity("Please select a valid email address.");
} else {
setCustomValidity("");
}
};
return (
<div className="App">
<Input
id="username"
type="string"
name="username"
label="username"
value={form["username"]}
onChange={handleChange}
required={true}
/>
<Input
id="password"
type="password"
name="password"
label="password"
value={form["password"]}
onChange={handleChange}
required={true}
/>
</div>
);
}
Input.tsx:
import React from "react";
type PropsType = {
id: string;
type: string;
name: string;
value: string;
label: string;
onChange: (event: React.SyntheticEvent) => void;
required: boolean;
};
/**
* Reusable standard Input component.
* #param {Object} props - Props passed from parent component containing input attributes.
* #return - Controlled input element.
*/
const Input: React.FC<PropsType> = ({
id,
type,
label,
onChange,
required,
...rest
}: PropsType) => (
<div className="input">
<input
id={id}
type={type}
placeholder=""
{...rest}
onChange={(event) => onChange(event)}
minLength={type === "password" ? 8 : undefined}
/>
<label htmlFor={id}>
{label}
{required && " *"}
</label>
</div>
);
export default Input;
As #Heretic Monkey pointed out, you should use event.target.setCustomValidity rather than destructuring the method to avoid losing the context of the function when it's called. See Why are certain function calls termed "illegal invocations" in JavaScript?.
Then, to properly type the event you may want to start by changing the onChange typing in Input.tsx.
// src/Input.tsx
type PropsType = {
//...
onChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
};
Then update your handleChange callback accordingly in App.tsx.
// src/App.tsx
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
//...
if (type === "email" && !emailPattern.test(value)) {
event.target.setCustomValidity("Please select a valid email address.");
} else {
event.target.setCustomValidity("");
}
}

How to listen to onChange of the Field component in React-Final-Form?

Redux-form "Field" component provides onChange property. A callback that will be called whenever an onChange event is fired from the underlying input. This callback allows to get "newValue" and "previousValue" for the Field.
React-final-form "Field" component doesn't have this property.
So, how I can get the same functionality?
React-final-form handles this functionality with a tiny external package.
Basically it is an additional component to add inside the form that binds to the element using its name:
<Field name="foo" component="input" type="checkbox" />
<OnChange name="foo">
{(value, previous) => {
// do something
}}
</OnChange>
The current documentation can be found here:
https://github.com/final-form/react-final-form-listeners#onchange
The idea under change detection is to subscribe to value changes of Field and call your custom onChange handler when value actually changes. I prepared simplified example where you can see it in action. Details are in MyField.js file.
As the result you can use it just as with redux-form:
<MyField
component="input"
name="firstName"
onChange={(val, prevVal) => console.log(val, prevVal)}
/>
2022 JANUARY UPDATE
While the code above still works (check the sandbox version) there is a case when the solutions requires more tweeks around it.
Here is an updated sandbox with an implementation via the hooks. It's based on a useFieldValue hook and OnChange component as a consumer of this hook. But the hook itself can be used separately when you need previous value between re-renders. This solution doesn't rely on meta.active of the field.
// useFieldValue.js
import { useEffect, useRef } from "react";
import { useField } from "react-final-form";
const usePrevious = (val) => {
const ref = useRef(val);
useEffect(() => {
ref.current = val;
}, [val]);
return ref.current;
};
const useFieldValue = (name) => {
const {
input: { value }
} = useField(name, { subscription: { value: true } });
const prevValue = usePrevious(value);
return [value, prevValue];
};
export default useFieldValue;
// OnChange.js
import { useEffect } from "react";
import useFieldValue from "./useFieldValue";
export default ({ name, onChange }) => {
const [value, prevValue] = useFieldValue(name);
useEffect(() => {
if (value !== prevValue) {
onChange(value, prevValue);
}
}, [onChange, value, prevValue]);
return null;
};
Another nice option is this answer: https://stackoverflow.com/a/56495998/3647991
I haven't used redux-form, but I added a super simple wrapper around the Field component to listen to onChange like this:
const Input = props => {
const {
name,
validate,
onChange,
...rest
} = props;
return (
<Field name={name} validate={validate}>
{({input, meta}) => {
return (
<input
{...input}
{...rest}
onChange={(e) => {
input.onChange(e); //final-form's onChange
if (onChange) { //props.onChange
onChange(e);
}
}}
/>
)}}
</Field>
);
};
One could use the Field's parse attribute and provide a function that does what you need with the value:
<Field
parse={value => {
// Do what you want with `value`
return value;
}}
// ...
/>
You need to use the ExternalModificationDetector component to listen for changes on the field component like this:
<ExternalModificationDetector name="abc">
{externallyModified => (
<BooleanDecay value={externallyModified} delay={1000}>
{highlight => (
<Field
//field properties here
/>
)}
</BooleanDecay>
)}
</ExternalModificationDetector>
By wrapping a stateful ExternalModificationDetector component in a
Field component, we can listen for changes to a field's value, and by
knowing whether or not the field is active, deduce when a field's
value changes due to external influences.
Via - React-Final-Form Github Docs
Here is a sandbox example provided in the React-Final-Form Docs: https://codesandbox.io/s/3x989zl866

Material ui textfield ith redux form issue

i'm having an issue here which i can't handle
I have a textfield which i made compatible with redux form
like this:
const renderTextField = props => (
<TextField {...props} />
);
and i'm using it like this:
<Field
id="searchCif"
name="searchCif"
component={renderTextField}
floatingLabelText={SEARCHVIEW_HINT_CIF}
floatingLabelFixed={false}
value
/>
Then i writing this in my container:
import { reduxForm } from 'redux-form/immutable';
import { connect } from 'react-redux';
// import { injectIntl } from 'react-intl';
import SearchDefaultView from './views/searchDefaultView';
import { requestCustomerInfo } from './actions/customerActions';
export const mapDispatchToProps = dispatch => (
{
requestCustomerInfo: formData =>
dispatch(requestCustomerInfo(formData))
}
);
const SearchDefaultReduxForm = reduxForm({
form: 'customerInfo', // a unique identifier for this form
})(SearchDefaultView);
const SearchDefaultContainer = connect(
null,
mapDispatchToProps
)(SearchDefaultReduxForm);
export default SearchDefaultContainer;
But when i'm writing the value and submit my form the form has NO VALUES. what am i missing?
From the dicumentation i used this:
const renderTextField = ({
input,
label,
meta: { touched, error },
...custom
}) =>
<TextField
hintText={label}
floatingLabelText={label}
errorText={touched && error}
{...input}
{...custom}
/>
const SearchDefaultView = (props) => {
const { requestCustomerInfo, handleSubmit } = props;
return (
<form onSubmit={handleSubmit(requestCustomerInfo)}>
<Menu
autoWidth={false}
style={styleSearchMenu}
>
<Divider />
<Field
id="searchCif"
name="searchCif"
component={renderTextField}
floatingLabelText={SEARCHVIEW_HINT_CIF}
floatingLabelFixed={false}
/>
<br />
<Field
id="searchAFM"
name="searchAFM"
component={renderTextField}
floatingLabelText={SEARCHVIEW_HINT_AFM}
floatingLabelFixed={false}
/>
<br />
<RaisedButton
type="submit"
fullWidth
primary
label={SEARCHVIEW_SEARCH}
/>
</Menu>
</form>
);
};
But it is showing me an error in compilation at ...custom
When you want to use a custom field for Redux-Form, Redux-form gives you access to both props like onChange etc, but also other meta-data (like if the form has been touched or not). These different kinds of props are grouped depending on type. The interesting part for you is that all the attributes associated with a normal input element (like onChange, value, type) are grouped in props.input. So to pass those down to the <TextField /> component you can't use the spread operator (... ) on props directly. You must use it on props.input.
const renderTextField = props => (
<TextField {...props.input} />
);
You may also have to deal with the fact that the onChange method that <TextField /> expects doesn't necessarily have the same signature as the onChange method that Redux-form provides you. So you may have to do some manual work to make them work together, similar to what I've outlined in this post. You'd have to read up on the documentation of both the onChange of Redux-Form and Material-UI TextField respectively.
You may also be interested to know that for material-ui components, there actually already exists a library that has done that manual work for you: redux-form-material-ui.
I think you are not using the onChange prop of the component.
onChange: Callback function that is fired when the textfield's value changes.
You should dispatch the change and update the data in redux container.
http://www.material-ui.com/#/components/text-field

Redux-form: display a list of errors on top of a page

I want to use Redux-form in a manner that changes input color & displays the actual error on top of the page. How do I access the list of current field errors outside the fields themselves?
You can't get the list of errors from outside of the render function given to the Field component. This is because errors are not stored in the redux store.
EDIT 26/12/2018 :
This answer is taking some age. ReduxForm now stores errors in the Redux store. Take a look to #nicoqh's answer which is using ReduxForm's selectors to get errors in any Redux connected component.
This answer is not totaly obsolete but it's not anymore the cleanest way to achieve this imho.
Solution 1: Use multiple Field for the same value
The first solution is to use multiple instance of Field for the same value. If multiple Field components have the same name and is connected to the same form name, they will all be connected to the same value and the same error handling.
So you can use a Field component and only render the error.
import React from 'react'
import {reduxForm} from 'redux-form'
const renderError = ({input, meta, ...props}) => (
<span {...props} className='error'>Error : {meta.error}</span>
)
const renderInput = ({input, meta, ...props}) => (
<input {...input} {...props} className={meta.error ? 'error' : null} />
)
const FormWithError = ({handleSubmit}) => (
<div>
<div className='errorContainer'>
<Field name='myInput' component={renderError} />
</div>
<form onSubmit={handleSubmit}>
<Field name='myInput' component={renderInput} />
</form>
</div>
)
const validate = (values, props) => {
const errors = {}
/* calculate errors here by appending theim to errors object */
return errors
}
export default reduxForm({form: 'myForm', validate})(FormWithError)
Solution 2: Use the global error prop
A second solution is to use the global error props, but you will have to display the errors from the container component using reduxForm.
Pay attention that this is a total antipatern ! Global error prop is for field independent errors.
import React from 'react'
import {reduxForm} from 'redux-form'
const renderInput = ({input, meta, ...props}) => (
<input {...input} {...props} className={meta.error ? 'error' : null} />
)
const FormWithError = ({handleSubmit, error}) => (
<div>
<div className='errorContainer'>
<span {...props} className='error'>Error : {error}</span>
</div>
<form onSubmit={handleSubmit}>
<Field name='myInput' component={renderInput} />
</form>
</div>
)
const validate = (values, props) => {
const errors = {}
/* calculate errors here by appending theim to errors object */
if(Object.keys(errors) > 0) {
//You can concatenate each error in a string
for(key in errors) errors._error += key + ': ' + errors[key]
//or juste put the errors object in the global error property
errors._error = {...errors}
}
return errors
}
export default reduxForm({form: 'myForm', validate})(FormWithError)
Solution 3: Get errors from the store
You always can get errors from the store by applying your validate function on the value presents in the store. It can be not performant for heavy validation because it run through validation at each render, so it runs twice when a value change and one for nothing if some other props changes. It can also be dificult to do with async validation.
import React from 'react'
import {reduxForm, formValueSelector} from 'redux-form'
import {connect} from 'redux'
const renderErrors = errors => {
const errorNodes = []
for(key in errors) errorNodes.push(<span className='error'>{key}: {errors[key]}</span>)
return errorNodes
}
const renderInput = ({input, meta, ...props}) => (
<input {...input} {...props} className={meta.error ? 'error' : null} />
)
let FormWithError = ({handleSubmit, values, ...otherProps}) => (
<div>
<div className='errorContainer'>
{renderErrors(validate(values, otherProps))}
</div>
<form onSubmit={handleSubmit}>
<Field name='myInput1' component={renderInput} />
<Field name='myInput2' component={renderInput} />
</form>
</div>
)
const validate = (values, props) => {
const errors = {}
/* calculate errors here by appending theim to errors object */
return errors
}
FormWithError = reduxForm({form: 'myForm', validate})(FormWithError)
FormWithError = connect(
state => formValueSelector('myForm')(state, 'myInput1', 'myInput2')
)(FormWithError)
A last solution can be to store the errors in the store by implementing the componentWillReceiveProps and dispatching an action to update a list of error in the store but i don't think it's really a good idea. It's better to keep simple stateless component to render a Field component.
EDIT 26/12/2018 :
This last "solution" wasn't a good one at the time I've posted it. But since componentWillReceiveProps is deprecated in React, it's not a solution at all. Please don't do this in you application. I don't delete this for history in case this answer was linked somewhere.
You can use the state selectors provided by redux-form.
In particular, getFormSubmitErrors will give you the submit validation errors:
import { getFormSubmitErrors } from 'redux-form';
// ...
const MyFormWithErrors = connect(state => ({
submitErrors: getFormSubmitErrors('my-form')(state)
}))(MyForm);
The original, unconnected MyForm component might look like this:
const MyForm = reduxForm({
form: 'my-form',
})(ManageUserProfile);
If you want to display the synchronous validation errors, you can use the getFormSyncErrors selector instead.

Categories

Resources