I am using formik and react-native-formik to create form. I am using my custom component for textinput in formik form.
My custom component code :
// import statements ...
class FormInput extends Component {
focus = () => { this.textInputField.focus(); }
render() {
const {props} = this;
return (
<TextInput
{...this.props}
placeholder={props.placeholder}
ref={(ref) => { this.textInputField = ref }}
style={{ height: 50, borderWidth: 1, margin: 5 }}
/>
)
}
}
export default FormInput
Then I create Form from below code :
const FormInput2 = compose(
handleTextInput,
withNextInputAutoFocusInput
)(FormInput);
const Form = withNextInputAutoFocusForm(View);
Then I have created original form as below :
<ScrollView
style={styles.container}
contentContainerStyle={styles.contentContainer}
keyboardShouldPersistTaps="handled"
>
<Formik
initialValues={{ firstName: '', email: '', password: '' }}
onSubmit={values => { }}
validationSchema={signUpValidationSchema}
render={({ values, handleChange, errors, setFieldTouched, touched, isValid, handleSubmit }) => (
<Form>
<FormInput2
icon="user"
placeholder="First Name"
value={values.firstName}
onChangeText={handleChange('firstName')}
onBlur={() => setFieldTouched('firstName')}
/>
{touched.firstName && errors.firstName && (
<CText style={{ fontSize: 10, color: 'red' }}>{errors.firstName}</CText>
)}
{/*
...
...
Others Fiels
*/}
<Button
title="SIGN UP"
disabled={!isValid}
color={Colors.primary}
onPress={handleSubmit}
/>
</Form>
)}
/>
</ScrollView>
But, I am getting done on each and every field keyboard return type. If I press done then form submit method fires.
Can anyone help me how can I implement auto focus ?
I have also tried exporting my custom component as below, but it is also not working :
export default withFormikControl(FormInput)
If anyone is also having the same error then here is the solution...
Custom component should have name property and the order of the component should be the same as initialValues.
Means if initialValues as below :
initialValues={{ firstName: '', lastName: '', email: '', phone: '', password: '', confirmPassword: '' }}
Then first should be firstName field then lastName, email and so on...
So I added name prop in custom component and worked autofocus.
<FormInput2
icon="user"
placeholder="First Name"
value={values.firstName}
label="First Name"
name="firstName" // added this
type="name"
onChangeText={handleChange('firstName')}
onBlur={() => setFieldTouched('firstName')}
/>
{
touched.firstName && errors.firstName && (
<CText style={{ fontSize: 10, color: 'red' }}>{errors.firstName}</CText>
)
}
<FormInput2
label="Last Name"
name="lastName" // added this
type="name"
icon="user"
placeholder="Last Name"
value={values.lastName}
onChangeText={handleChange('lastName')}
onBlur={() => setFieldTouched('lastName')}
/>
{
touched.lastName && errors.lastName && (
<CText style={{ fontSize: 10, color: 'red' }}>{errors.lastName}</CText>
)
}
Related
In react native I want to make a dynamic controller component. But i cant access errors with it. I using "react-hook-form" for form elements. So Its my component :
const {
control,
handleSubmit,
formState: {errors},
setValue,
} = useForm();
const DynamicController = ({req, pattern, name, label}) => {
return (
<>
<Text style={[t.textBase]}>{label}</Text>
<Controller
control={control}
defaultValue=""
rules={{
required: {
value: true,
message: 'Bu alan boş bırakılamaz!',
},
}}
render={({field: {onChange, onBlur, value}}) => (
<Input
errorText={errors[name].message}
error={errors[name]}
onBlur={onBlur}
placeholder={label}
onChangeText={onChange}
value={value}
/>
)}
name={name}
/>
</>
);
};
My Input Component is basicly simple input. My problem is when i give error name like that example i cant access errors.
Its how i use my component :
<DynamicController
label="Email"
name="Email"
pattern={true}
req={true}
/>
When i dont fill the element and log the submit its not showing any error. Its simple passing validate. So what can i do where do i make wrong ? thank you for answerings!!!
Is your Input a custom wrapper? If not, a better way do this using react-hook-form would be:
const {
control,
handleSubmit,
formState: {errors},
setValue,
} = useForm(
defaultValues: {
firstName: '', // form fields should be populated here so that the error can be displayed appropriately
lastName: ''
}
);
const DynamicController = ({req, pattern, name, label}) => {
return (
<>
<Text style={[t.textBase]}>{label}</Text>
<Controller
control={control}
defaultValue=""
rules={{
required: {
value: true,
message: 'Bu alan boş bırakılamaz!',
},
}}
render={({field: {onChange, onBlur, value}}) => (
<Input
onBlur={onBlur}
placeholder={label}
onChangeText={onChange}
value={value}
/>
)}
name={name}
/>
{errors[name] && <Text>This is required.</Text>}
</>
);
};
import React, { Component, useState } from "react";
export class Register extends Component {
constructor(props) {
super(props);
this.state = {
In: "",
lbs: "",
age: "",
gender: "",
};
}
render() {
//BMR Calculator
const { In } = this.state;
const { lbs } = this.state;
const { age } = this.state;
const { gender } = this.state;
return (
<SafeAreaView>
<View>
<TextInput
label="username"
onChangeText={(name) => this.setState({ name })}
/>
<TextInput
label="email"
onChangeText={(email) => this.setState({ email })}
/>
<TextInput
label="password"
onChangeText={(password) => this.setState({ password })}
/>
</View>
<Text>Weight</Text>
<TextInput
placeholder="In"
keyboardType="numeric"
maxLength="2"
onChangeText={(In) => this.setState({ In })}
style={styles.heightImperialIn}
/>
<Text>Weight</Text>
<TextInput
placeholder="lbs"
keyboardType="numeric"
maxLength="3"
value={lbs}
onChangeText={(lbs) => this.setState(+e.target.value)}
//onChangeText={(lbs) => this.setState({ lbs })}
style={styles.weigthImperial}
/>
<Text style={styles.txtAge}>Age</Text>
<TextInput
placeholder="18"
keyboardType="numeric"
maxLength="2"
value={age}
onChange={(age) => this.setState(+e.target.value)}
//onChangeText={(age) => this.setState({ age })}
style={styles.ageImperial}
/>
<Text>Gender</Text>
<View style={{ top: hp("10%") }}>
<RNPickerSelect
style={pickerStyle}
placeholder={{
label: "Select a gender",
value: null,
}}
onValueChange={(gender) => this.setState({ gender })}
items={[
{ label: "Male", value: "male" },
{ label: "Female", value: "female" },
]}
/>
</View>
</SafeAreaView>
Hey, everyone, so I have these two equations for a BMR calculator :
male=66+(6.2lbs)+(12.7In)-(6.76*age)
female=655.1+(4.35lbs)+(4.7In)-(4.7*age)
My question is how do I get the values of lbs, In and age from the text inputs above and inserted them in one of the two equations, depending on the gender selected by the user, and display the result?
Thanks
Here is the working example: Expo Snack
import React, { Component, useState } from 'react';
import { Text, View, StyleSheet, SafeAreaView, TextInput } from 'react-native';
import Constants from 'expo-constants';
import RNPickerSelect from 'react-native-picker-select';
// You can import from local files
import AssetExample from './components/AssetExample';
// or any pure javascript modules available in npm
import { Card } from 'react-native-paper';
export default class Register extends Component {
constructor(props) {
super(props);
this.state = {
In: 0,
lbs: 0,
age: 0,
gender: '',
};
}
render() {
//BMR Calculator
const { In, lbs, age, gender } = this.state;
return (
<SafeAreaView>
<View>
<TextInput
label="username"
onChangeText={(name) => this.setState({ name })}
/>
<TextInput
label="email"
onChangeText={(email) => this.setState({ email })}
/>
<TextInput
label="password"
onChangeText={(password) => this.setState({ password })}
/>
</View>
<Text>Weight</Text>
<TextInput
placeholder="In"
keyboardType="numeric"
maxLength="2"
onChangeText={(In) => this.setState({ In: Number(In) })}
style={styles.heightImperialIn}
/>
<Text>Weight</Text>
<TextInput
placeholder="lbs"
keyboardType="numeric"
maxLength="3"
value={lbs}
onChangeText={(lbs) => {
console.log(typeof Number(lbs));
this.setState({ lbs: Number(lbs) });
}}
/>
<Text style={styles.txtAge}>Age</Text>
<TextInput
placeholder="18"
keyboardType="numeric"
maxLength="2"
onChangeText={(age) => this.setState({ age: Number(age) })}
style={styles.heightImperialIn}
/>
<Text>Gender</Text>
<View style={{}}>
<RNPickerSelect
style={{}}
placeholder={{
label: 'Select a gender',
value: null,
}}
onValueChange={(gender) => this.setState({ gender })}
items={[
{ label: 'Male', value: 'male' },
{ label: 'Female', value: 'female' },
]}
/>
</View>
<Text>Result for {gender.toUpperCase()}</Text>
{gender ? (
gender === 'male' ? (
66 + 6.2 * lbs + 12.7 * In + 6.76 * age
).toFixed(2) : (
65.51 + 4.35 * lbs + 4.7 * In - 4.7 * age
).toFixed(2)
) : (
<Text>Select the values</Text>
)}
</SafeAreaView>
);
}
}
const styles = StyleSheet.create({
});
I want to trigger formik errors and touched whenever the input is clicked and the value is not correct .
I pass formik props to the input component like this :
const initialValues = {
title: ''
};
const validationSchema = yup.object({
title: yup.string().max(50, 'less than 50 words !!').required('required !!')
});
function Add() {
<Formik initialValues={initialValues} onSubmit={onSubmit} validationSchema={validationSchema}>
{(props) => {
return (
<Form>
<AddTitle props={props} />
</Form>
);
}}
</Formik>
);
}
Here I'm trying to display error message whenever the input is touched and there is an error with it like this :
import { Input } from 'antd';
function AddTitle(props) {
console.log(props.props);
return (
<Field name="title">
{() => {
return (
<Input
onChange={(e) => {
props.props.setFieldValue('title', e.target.value)
}}
/>
);
}}
</Field>
<ErrorMessage name="title" />
<P>
{props.props.touched.title && props.props.errors.title && props.props.errors.title}
</P>
</React.Fragment>
);
}
But ErrorMessage and the paragraph below it doesn't work when the input is touched and empty .
In console log it shows that input doesn't handle formik touched method and it only triggers the error for it :
touched:
__proto__: Object
errors:
title: "less than 50 words !"
__proto__: Object
How can I use ErrorMessage properly while passing in formik props to a component and using a third library for inputs ?
Fixed the issue by adding the onBlur to the input and ErrorMessage is working fine :
<Field name="title">
{() => {
return (
<Input
onBlur={() => props.props.setFieldTouched('title')}
onChange={(e) => {
props.props.setFieldValue('title', e.target.value);
}}
/>
);
}}
</Field>
<P class="mt-2 text-danger">
<ErrorMessage name="title" />
</P>
There is a basic Formik form:
<Formik
initialValues={{ email: '', color: 'red', firstName: '' }}
onSubmit={(values, actions) => {
setTimeout(() => {
alert(JSON.stringify(values, null, 2));
actions.setSubmitting(false);
}, 1000);
}}
render={props => (
<form onSubmit={props.handleSubmit}>
<Field type="email" name="email" placeholder="Email" />
<div>other inputs ... </div>
<button type="submit">Submit</button>
</form>
)}
/>
When any input in it changes (not submits, but changes) - I need to update another component that is outside of <Formik />. The "outside" component should receive all form data.
Is there some way to do it without adding separate change handler for each individual input of a form? Or the solution is to try to insert "outside" component inside <Formik />?
Formik provides values object which you can make use of to get values outside.
const App = () => {
const initialValues = { email: '', color: 'red', firstName: '' }
const [formValues, setformValues] = useState(initialValues);
const getFormData = values => {
// access values here
};
return (
<div>
<h1>Formik take values outside</h1>
<Formik
initialValues={initialValues}
...
>
{props => {
setformValues(props.values); // store values in state 'formValues'
getFormData(props.values); // or use any function to get values like this
return (
<form onSubmit={props.handleSubmit}>
...
Working demo in codesandbox here
export const LoginForm: React.FC<Values> = () => {
const initialValues = { user: "", password: "" };
const [formValues, setformValues] = React.useState(initialValues);
return (
<div>{formValues.user}</div>
<Formik
initialValues={initialValues}
validationSchema={ValidationSchema}
onSubmit={(values, { setSubmitting, resetForm }) => {
setTimeout(() => {
//alert(JSON.stringify(values, null, 2));
resetForm();
setSubmitting(false);
setformValues(values);
}, 500);
}}
>
{({
values,
errors,
touched,
handleChange,
handleBlur,
handleSubmit,
isSubmitting,
}) => {
return (
<>
<TextField
label="Usuario"
name="user"
value={values.user}
onChange={handleChange}
onBlur={handleBlur}
fullWidth
color={touched.user && errors.user ? "primary" : "secondary"}
/>
<Error touched={touched.user} message={errors.user} />
</>
<div className="pane-form__submit">
<Button
className={classes.customHoverFocus}
variant="contained"
type="submit"
disabled={isSubmitting}
label="CONTINUAR"
>Continuar</Button>
</div>
</Form>
)
}}
</Formik>
</>
);
};
I have used redux-form for the form. I have developed a wizard considering an example from the documentation. The wizard is working but when i go to step 2 i.e SyncAccount component which has the form and fill that form and hit the next button to go to another step and hit the back button to go back to the SyncAccount component then the form is cleared. I have used destroyOnUnmount but it is not solving my issue either. What have i missed?
only second page i.e SyncAccount has only form. First and third do not have.
here is the code
AccountSteps.js
const mapDispatchToProps = dispatch => ({
getUser: () => dispatch(getCurrentUser()),
postWizard: (userObj, wizardType) =>
dispatch(postWizardData(userObj, wizardType)),
updateWizard: (userObj, wizardType) =>
dispatch(updateWizardData(userObj, wizardType)),
});
const mapStateToProps = state => ({
userData: state.userReducer,
wizard: state.wizardReducer,
});
class AccountSteps extends React.Component<{
user: Object,
wizard: Object,
postWizard: Function,
updateWizard: Function,
getUser: Function
}> {
constructor(props) {
super(props);
this.state = {
page: 0,
steps: [
{ steps: 0, label: 'Privacy' },
{ steps: 1, label: 'Sync Your Account' },
{ steps: 2, label: 'Install Extension' },
],
};
}
componentDidMount() {
this.props.getUser();
}
static getDerivedStateFromProps(nextProps, prevState) {
const { wizard } = nextProps;
if (!isEmpty(wizard) && wizard.page !== prevState.page) {
return {
page: wizard.page,
};
}
return null;
}
nextPage = (userObj, type) => {
this.props.postWizard(userObj, type);
};
previousPage = (wizardData) => {
console.log('wizardData', wizardData);
this.props.updateWizard(wizardData);
};
render() {
const { page, steps } = this.state;
return (
<Wrapper>
<CardWrapper>
<Stepper activeStep={page} alternativeLabel>
{steps.map(step => (
<Step key={step.steps}>
<StepLabel>{step.label}</StepLabel>
</Step>
))}
</Stepper>
{page === 0 && (
<Privacy
{...this.props}
activeStep={page}
back={this.previousPage}
next={this.nextPage}
/>
)}
{page === 1 && (
<SyncAccount
{...this.props}
activeStep={page}
back={this.previousPage}
next={this.nextPage}
/>
)}
{page === 2 && (
<Extension
{...this.props}
activeStep={page}
back={this.previousPage}
next={this.nextPage}
/>
)}
</CardWrapper>
</Wrapper>
);
}
}
export default connect(
mapStateToProps,
mapDispatchToProps,
)(AccountSteps);
SyncAccount.js
const SyncAccount = ({
user,
emailProvider,
handleProviderChange,
handleChange,
...props
}: {
user: Object,
emailProvider: string,
handleProviderChange: Function,
handleChange: Function
}) => {
console.log('props in Sync', user.email);
return (
<SyncWrapper>
<IconsWrapper>
<CustomSync style={{ fontSize: 30 }} color="action" />
<Mail style={{ fontSize: 50 }} color="secondary" />
</IconsWrapper>
<p>Please enter your email address which you want to sync with us.</p>
<FormWrapper>
<span>Email Provider: </span>
<RadioGroup
aria-label="emailProvider"
name="provider"
style={{ display: 'flex', flexDirection: 'row' }}
value={user.provider}
onChange={handleChange}
>
<FormControlLabel
value="google"
control={<Radio color="primary" />}
label="Google"
/>
<FormControlLabel
value="imap"
control={<Radio color="primary" />}
label="IMAP"
/>
</RadioGroup>
<StyledField
label="Email"
id="email"
name="email"
type="email"
value={user.email}
onChange={handleChange}
placeholder="Email"
component={GTextField}
required
/>
<StyledField
label="Password"
id="password"
name="password"
type="password"
value={user.password}
onChange={handleChange}
placeholder="Password"
component={GPasswordField}
required
/>
<StyledField
label="Job title"
id="jobTitle"
name="job_title"
value={user.job_title}
onChange={handleChange}
placeholder="Job Title"
component={GAutoCompleteField}
required
/>
<Footer
{...props}
userObj={user}
type="sync"
wizardData={{ step_index: 0, wizard_name: 'privacy' }}
disabled={user && (!user.email || !user.password || !user.job_title)}
/>
</FormWrapper>
</SyncWrapper>
);
};
export default enhance(SyncAccount);
enhance.js
const requiredFields = {
email: 'Email',
password: 'Password',
job_title: 'Job Title',
};
const withReduxForm = reduxForm({
form: 'syncAccount',
fields: requiredFields,
destroyOnUnmount: false,
forceUnregisterOnUnmount: true,
validate,
});
const mapStateToProps = state => ({
wizard: state.wizardReducer,
});
const enhance = compose(
connect(
mapStateToProps,
null,
),
withReduxForm,
withState('user', 'updateUser', {
email: '',
password: '',
job_title: '',
provider: 'google',
wizard_name: 'extension',
step_index: 2,
}),
withHandlers({
handleChange: props => (ev) => {
if (ev.target) {
return props.updateUser({
...props.user,
[ev.target.name]: ev.target.value,
});
}
return props.updateUser({
...props.user,
job_title: ev.name,
});
},
}),
);
export default enhance;