Big News: Socket raises $60M Series C at a $1B valuation to secure software supply chains for AI-driven development.Announcement
Sign In

react-native-formik

Package Overview
Dependencies
Maintainers
1
Versions
31
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

react-native-formik - npm Package Compare versions

Comparing version
1.6.0
to
1.7.0
+3
.prettierrc
{
"singleQuote": false
}
# Formik and React Native Formik Step by Step
This is the livecoding session done at [a meetup](https://www.meetup.com/fr-FR/ReactJS-Paris/events/256773363/) to explain how easy it is to implement forms with `formik`, and why we built react-native-formik to make it even easier.
- [Step 0: let's start with a simple useless form](#step-0-lets-start-with-a-simple-useless-form)
- [Step 1: wrap it in Formik](#step-1-wrap-it-in-formik)
- [Step 2: connect our inputs to the formik context](#step-2-connect-our-inputs-to-the-formik-context)
- [Step 3: let's submit!](#step-3-lets-submit)
- [Step 4: So easy to add validation](#step-4-so-easy-to-add-validation)
- [Step 5: Show errors only when necessary](#step-5-show-errors-only-when-necessary)
- [Step 6: Introducing react-native-formik](#step-6-introducing-react-native-formik)
- [Step 7: handling types of inputs](#step-7-handling-types-of-inputs)
- [Step 8: auto focus the next input](#step-8-auto-focus-the-next-input)
- [Step 9: handle any component](#step-9-handle-any-component)
- [Step 10: Questions?](#step-10-questions)
## Step 0: let's start with a simple useless form
```javascript
import React from "react";
import { Button, TextInput, View } from "react-native";
import { TextField } from "react-native-material-text-field";
export default () => (
<View style={{ padding: 10 }}>
<TextField label="email" />
<TextField label="password" />
<Button onPress={() => alert("SUBMIT WOUHOU")} title="SUBMIT" />
</View>
);
```
![image](https://user-images.githubusercontent.com/4534323/50046957-da65f280-00ac-11e9-8aa1-7a7a5b5b6336.png)
Obviously this won't do much, let's wrap it in Formik.
## Step 1: wrap it in Formik
`formik` provides context to the form that we can display in a `Text` component.
```javascript
import React from "react";
import { Text, View } from "react-native";
import { Formik } from "formik";
import { TextField } from "react-native-material-textfield";
export default props => (
<Formik>
{props => {
return (
<View style={{ padding: 10 }}>
<TextField label="email" />
<TextField label="password" />
<Text style={{ fontSize: 20 }}>{JSON.stringify(props, null, 2)}</Text>
</View>
);
}}
</Formik>
);
```
![image](https://user-images.githubusercontent.com/4534323/50047009-accd7900-00ad-11e9-9488-990069375f19.png)
As we can see, `formik` exposes the whole form state, but typing text doesn't change anything. Indeed, we have to explicitely tell our inputs to modify the formik context when their values change.
## Step 2: connect our inputs to the formik context
We'll add `onChangeText={text => props.setFieldValue("email", text)}` on the inputs:
```javascript
import React from "react";
import { Text, View } from "react-native";
import { Formik } from "formik";
import { TextField } from "react-native-material-textfield";
export default props => (
<Formik>
{props => {
return (
<View style={{ padding: 10 }}>
<TextField
label="email"
onChangeText={text => props.setFieldValue("email", text)}
/>
<TextField
label="password"
onChangeText={text => props.setFieldValue("password", text)}
/>
<Text style={{ fontSize: 20 }}>{JSON.stringify(props, null, 2)}</Text>
</View>
);
}}
</Formik>
);
```
Awesome, now the values are updated directly!
![image](https://user-images.githubusercontent.com/4534323/50047036-4432cc00-00ae-11e9-95c5-8a3fedac0f34.png)
## Step 3: let's submit!
We can now add a `Button` to submit:
```javascript
import React from "react";
import { Button, Text, View } from "react-native";
import { Formik } from "formik";
import { TextField } from "react-native-material-textfield";
export default props => (
<Formik onSubmit={values => alert(JSON.stringify(values, null, 2))}>
{props => {
return (
<View style={{ padding: 10 }}>
<TextField
label="email"
onChangeText={text => props.setFieldValue("email", text)}
/>
<TextField
label="password"
onChangeText={text => props.setFieldValue("password", text)}
/>
<Button onPress={props.handleSubmit} title="SUBMIT" />
<Text style={{ fontSize: 20 }}>{JSON.stringify(props, null, 2)}</Text>
</View>
);
}}
</Formik>
);
```
![image](https://user-images.githubusercontent.com/4534323/50047050-a12e8200-00ae-11e9-9635-2e649fe11f2a.png)
## Step 4: So easy to add validation
We can create a `validationSchema` with the JS lib `yup` and bring validation to our form with ease!
Plus it's easy to add any custom validation message you'd like.
Since `react-native-material-text-field` takes `error` as prop, let's pass it
```javascript
import React from "react";
import { Button, Text, View } from "react-native";
import { Formik } from "formik";
import { TextField } from "react-native-material-textfield";
import * as Yup from "yup";
const validationSchema = Yup.object().shape({
email: Yup.string()
.required()
.email("Welp, that's not an email"),
password: Yup.string()
.required()
.min(6, "That can't be very secure")
});
export default props => (
<Formik
onSubmit={values => alert(JSON.stringify(values, null, 2))}
validationSchema={validationSchema}
>
{props => {
return (
<View style={{ padding: 10 }}>
<TextField
label="email"
onChangeText={text => props.setFieldValue("email", text)}
error={props.errors.email}
/>
<TextField
label="password"
onChangeText={text => props.setFieldValue("password", text)}
error={props.errors.password}
/>
<Button onPress={props.handleSubmit} title="SUBMIT" />
<Text style={{ fontSize: 20 }}>{JSON.stringify(props, null, 2)}</Text>
</View>
);
}}
</Formik>
);
```
`formik` automatically validates your fields and prevents submission if there are errors.
![image](https://user-images.githubusercontent.com/4534323/50047106-81e42480-00af-11e9-8551-cc91b2813c1a.png)
Now we have an issue though, the errors display as soon as I type anything, quite annoying for the user.
## Step 5: Show errors only when necessary
The best practice is to show an error only if an input has been focused then left (we'll mark it as "touched" in formik in that case) or if the form has been submitting.
The good news is we can handle that with what `formik` gives us:
We can add on the input:
```javascript
error={
props.touched.email || props.submitCount > 0
? props.errors.email
: null
}
```
And the code becomes:
```javascript
import React from "react";
import { Button, Text, View } from "react-native";
import { Formik } from "formik";
import { TextField } from "react-native-material-textfield";
import * as Yup from "yup";
const validationSchema = Yup.object().shape({
email: Yup.string()
.required()
.email("Welp, that's not an email"),
password: Yup.string()
.required()
.min(6, "That can't be very secure")
});
export default props => (
<Formik
onSubmit={values => alert(JSON.stringify(values, null, 2))}
validationSchema={validationSchema}
>
{props => {
return (
<View style={{ padding: 10 }}>
<TextField
label="email"
onChangeText={text => props.setFieldValue("email", text)}
onBlur={() => props.setFieldTouched("email")}
error={
props.touched.email || props.submitCount > 0
? props.errors.email
: null
}
/>
<TextField
label="password"
onChangeText={text => props.setFieldValue("password", text)}
onBlur={() => props.setFieldTouched("password")}
error={
props.touched.password || props.submitCount > 0
? props.errors.password
: null
}
/>
<Button onPress={props.handleSubmit} title="SUBMIT" />
<Text style={{ fontSize: 20 }}>{JSON.stringify(props, null, 2)}</Text>
</View>
);
}}
</Formik>
);
```
Ok, we talked about reducing boilerplate, but I see a lot of code repetition between the password and the email input. We need a refactoring, and react-native-formik makes it easy for you.
## Step 6: Introducing react-native-formik
With react-native-formik:
```javascript
onChangeText={text => props.setFieldValue("password", text)}
onBlur={() => props.setFieldTouched("password")}
error={
props.touched.password || props.submitCount > 0
? props.errors.password
: null
}
```
is equivalent to:
```javascript
name = "password";
```
Thanks to a [High Order Component](https://reactjs.org/docs/higher-order-components.html) called `handleTextInput`.
The code becomes cleaner:
```javascript
import React from "react";
import { Button, Text, View } from "react-native";
import { Formik } from "formik";
import { handleTextInput } from "react-native-formik";
import { TextField } from "react-native-material-textfield";
import * as Yup from "yup";
const FormikInput = handleTextInput(TextField);
const validationSchema = Yup.object().shape({
email: Yup.string()
.required()
.email("Welp, that's not an email"),
password: Yup.string()
.required()
.min(6, "That can't be very secure")
});
export default props => (
<Formik
onSubmit={values => alert(JSON.stringify(values, null, 2))}
validationSchema={validationSchema}
>
{props => {
return (
<View style={{ padding: 10 }}>
<FormikInput label="email" name="email" />
<FormikInput label="password" name="password" />
<Button onPress={props.handleSubmit} title="SUBMIT" />
<Text style={{ fontSize: 20 }}>{JSON.stringify(props, null, 2)}</Text>
</View>
);
}}
</Formik>
);
```
## Step 7: handling types of inputs
But now we're lacking a few essential features. You probably noticed some unacceptable behaviors in the form. Yes:
- the password input is not hidden
- it's also auto capitalized
- the email input doesn't show the `@` in the keyboard
Whenever you build a form, there are a few input props you should never forget to set.
The good news is with `react-native-formik`, you can just add a `type` to your input:
```javascript
<FormikInput label="email" name="email" type="email" />
```
Much better:
![image](https://user-images.githubusercontent.com/4534323/50047210-67ab4600-00b1-11e9-9282-7f4f0e92739f.png)
## Step 8: auto focus the next input
When typing on a form, users can be used to click "next" on the keyboard to automatically go to the next input and submit on the last input.
In React Native, to do so, you have to change `returnKeyType` on your inputs, and add `onSubmitEditing={() => this.nextInputRef.focus()}` on every input. Which requires you to add refs to all your inputs.
With `react-native-formik`, we can use two HOC:
- `withNextInputAutoFocusForm` on the view containing all inputs
- `withNextInputAutoFocusInput` on the inputs
as long as the decorated input:
- is a class
- has a focus metho
With code as small as this, we have this behavior handled:
```javascript
import React from "react";
import { Button, Text, View } from "react-native";
import { Formik } from "formik";
import { compose } from "recompose";
import {
handleTextInput,
withNextInputAutoFocusForm,
withNextInputAutoFocusInput
} from "react-native-formik";
import { TextField } from "react-native-material-textfield";
import * as Yup from "yup";
const FormikInput = compose(
handleTextInput,
withNextInputAutoFocusInput
)(TextField);
const InputsContainer = withNextInputAutoFocusForm(View);
const validationSchema = Yup.object().shape({
email: Yup.string()
.required()
.email("Welp, that's not an email"),
password: Yup.string()
.required()
.min(6, "That can't be very secure")
});
export default props => (
<Formik
onSubmit={values => alert(JSON.stringify(values, null, 2))}
validationSchema={validationSchema}
>
{props => {
return (
<InputsContainer style={{ padding: 10 }}>
<FormikInput label="email" name="email" type="email" />
<FormikInput label="password" name="password" type="password" />
<Button onPress={props.handleSubmit} title="SUBMIT" />
<Text style={{ fontSize: 20 }}>{JSON.stringify(props, null, 2)}</Text>
</InputsContainer>
);
}}
</Formik>
);
```
## Step 9: handle any component
Well there's more to life than TextInputs. But for any component you want to have, you can use `withFormikControl` to easily manage its state in `formik`.
`withFormikControl` will pass the following props:
```javascript
{
value: ValueType,
setFieldValue: (value: ValueType) => void,
error: ?string,
setFieldTouched: () => void
}
```
So, for instance you could have a simple `Switch` component:
```javacript
import React from "react";
import { Text, Switch as RNSwitch, View } from "react-native";
import { withFormikControl } from "react-native-formik";
class Switch extends React.PureComponent {
render() {
const { error, value, setFieldValue, label } = this.props;
return (
<View
style={{
flexDirection: "row",
alignItems: "center",
marginTop: 10
}}
>
<RNSwitch
value={value}
ios_backgroundColor={error ? "red" : "transparent"}
onValueChange={setFieldValue}
/>
<Text style={{ marginLeft: 10, flex: 1 }}>{label}</Text>
</View>
);
}
}
export default withFormikControl(Switch);
```
And easily add it to your form:
```javascript
import React from "react";
import { Button, Text, View } from "react-native";
import { Formik } from "formik";
import { compose } from "recompose";
import {
handleTextInput,
withNextInputAutoFocusForm,
withNextInputAutoFocusInput
} from "react-native-formik";
import { TextField } from "react-native-material-textfield";
import * as Yup from "yup";
import Switch from "./Switch";
const FormikInput = compose(
handleTextInput,
withNextInputAutoFocusInput
)(TextField);
const InputsContainer = withNextInputAutoFocusForm(View);
const validationSchema = Yup.object().shape({
email: Yup.string()
.required()
.email("Welp, that's not an email"),
password: Yup.string()
.required()
.min(6, "That can't be very secure"),
star: Yup.boolean()
.required()
.oneOf([true], "Feel free to submit an issue if you found some bugs!")
});
export default props => (
<Formik
onSubmit={values => alert(JSON.stringify(values, null, 2))}
validationSchema={validationSchema}
>
{props => {
return (
<InputsContainer style={{ padding: 10 }}>
<FormikInput label="email" name="email" type="email" />
<FormikInput label="password" name="password" type="password" />
<Switch
label="If you like the repo, have you starred it 😁?"
name="star"
/>
<Button onPress={props.handleSubmit} title="SUBMIT" />
<Text style={{ fontSize: 20 }}>{JSON.stringify(props, null, 2)}</Text>
</InputsContainer>
);
}}
</Formik>
);
```
![image](https://user-images.githubusercontent.com/4534323/50047321-19974200-00b3-11e9-929a-f5e73dc7bab3.png)
## Step 10: Questions?
Feel free to star this repo if you like it as well as [formik](https://github.com/jaredpalmer/formik)!
Please open issues if something's wrong with it, you can also ping me on [Twitter @almouro](https://twitter.com/almouro)
import React from "react";
import { compose } from "recompose";
import { TextInput } from "react-native";
import { mount } from "enzyme";
import { withErrorIfNeeded } from "../..";
import withFormikMock from "../testUtils/withFormikMock";
console.error = jest.fn();
const TOUCHED_INPUT_NAME = "TOUCHED_INPUT_NAME";
const TOUCHED_INPUT_ERROR = "TOUCHED_INPUT_ERROR";
const UNTOUCHED_INPUT_NAME = "UNTOUCHED_INPUT_NAME";
const UNTOUCHED_INPUT_ERROR = "UNTOUCHED_INPUT_ERROR";
const formikContext = {
errors: {
[TOUCHED_INPUT_NAME]: TOUCHED_INPUT_ERROR,
[UNTOUCHED_INPUT_NAME]: UNTOUCHED_INPUT_ERROR
},
touched: {
[TOUCHED_INPUT_NAME]: true
},
submitCount: 0
};
const Input = compose(
withFormikMock(formikContext),
withErrorIfNeeded
)(TextInput);
const testInputError = (inputName, expectedError) => {
const input = mount(<Input name={inputName} />);
expect(input.find(TextInput).props().error).toEqual(expectedError);
};
describe("withError", () => {
it("displays error for all inputs if the form is submitted", () => {
formikContext.submitCount = 1;
testInputError(UNTOUCHED_INPUT_NAME, UNTOUCHED_INPUT_ERROR);
testInputError("valid input", undefined);
});
it("displays error for touched inputs if the form is not submitted", () => {
formikContext.submitCount = 0;
testInputError(UNTOUCHED_INPUT_NAME, undefined);
testInputError(TOUCHED_INPUT_NAME, TOUCHED_INPUT_ERROR);
});
it("keeps other props", () => {
const wrapper = mount(<Input name="inputName" someProp="someValue" />);
expect(wrapper.find(TextInput).props().someProp).toEqual("someValue");
});
it("allows overriding error prop", () => {
const erroredInput = mount(
<Input name={TOUCHED_INPUT_NAME} error="override!!" />
);
expect(erroredInput.find(TextInput).props().error).toEqual("override!!");
});
});
import { compose, withProps } from "recompose";
import { connect } from "formik";
import withInputTypeProps from "./withInputTypeProps";
import withFormikControl from "./withFormikControl";
const handleTextInput = compose(
withFormikControl,
withInputTypeProps,
connect,
withProps(
({
formik: { isSubmitting },
setFieldValue,
setFieldTouched,
onChangeText,
onBlur
}) => ({
onChangeText: text => {
setFieldValue(text);
if (onChangeText) onChangeText(text);
},
onBlur: () => {
// validate onBlur only while not submitting
// this prevents validating twice in succession when clicking 'done' on keyboard - first onSubmitEditing, then onBlur
setFieldTouched(true, !isSubmitting);
if (onBlur) onBlur();
}
})
)
);
export default handleTextInput;
import { compose, mapProps } from "recompose";
import { connect } from "formik";
import withError from "./withError";
import withTouched from "./withTouched";
const withErrorIfNeeded = compose(
withError,
withTouched,
connect,
mapProps(({ formik: { submitCount }, error, touched, ...props }) => {
const shouldDisplayError = touched || submitCount > 0;
return {
touched,
error: shouldDisplayError ? error : undefined,
...props
};
})
);
export default withErrorIfNeeded;
import { mapProps, compose } from "recompose";
import { connect } from "formik";
import { get } from "lodash";
import withErrorIfNeeded from "./withErrorIfNeeded";
const withFormikControl = compose(
withErrorIfNeeded,
connect,
mapProps(
({
formik: { values, setFieldValue, setFieldTouched },
name,
...props
}) => ({
value: get(values, name),
setFieldValue: (value, ...args) => setFieldValue(name, value, ...args),
setFieldTouched: (value, ...args) =>
setFieldTouched(name, value, ...args),
name,
...props
})
)
);
export default withFormikControl;
+10
-0

@@ -23,2 +23,8 @@ import * as React from 'react';

export interface withFormikControlProps {
error?: string;
value: string;
onChange: string;
}
export type makeInputGreatAgainProps = makeReactNativeFieldProps &

@@ -70,2 +76,6 @@ setFormikInitialValueProps &

export function withFormikControl<Props>(
WrappedComponent: React.ComponentType<Props>
): React.ComponentClass<Props & makeInputGreatAgainProps>;
export default makeInputGreatAgain;
+10
-1
import { compose } from "recompose";
import handleTextInput from "./src/handleTextInput";
import setFormikInitialValue from "./src/setFormikInitialValue";
import withError from "./src/withError";
import withErrorIfNeeded from "./src/withErrorIfNeeded";
import withFocus from "./src/withFocus";
import withFormik from "./src/withFormik";
import withFormikControl from "./src/withFormikControl";
import withInputTypeProps from "./src/withInputTypeProps";

@@ -11,3 +14,6 @@ import withTouched from "./src/withTouched";

import makeReactNativeField from "./src/makeReactNativeField";
import { withNextInputAutoFocusForm, withNextInputAutoFocusInput } from "./src/withNextInputAutoFocus";
import {
withNextInputAutoFocusForm,
withNextInputAutoFocusInput
} from "./src/withNextInputAutoFocus";

@@ -25,2 +31,3 @@ const makeInputsGreatAgain = compose(

export {
handleTextInput,
makeInputsGreatAgain,

@@ -30,4 +37,6 @@ makeReactNativeField,

withError,
withErrorIfNeeded,
withFocus,
withFormik,
withFormikControl,
withInputTypeProps,

@@ -34,0 +43,0 @@ withTouched,

+5
-4

@@ -1,9 +0,10 @@

const path = require('path');
const path = require("path");
module.exports = {
preset: 'react-native',
preset: "react-native",
transformIgnorePatterns: [
'node_modules/(?!(react-native|react-native-button|react-native-core-library|@bam.tech/[w-]*|static-container|react-native-tab-view)|react-native-iphone-x-helper/)',
"node_modules/(?!(react-native|react-native-button|react-native-core-library|@bam.tech/[w-]*|static-container|react-native-tab-view)|react-native-iphone-x-helper/)"
],
setupFiles: [path.join(__dirname, './jest/preamble.js')],
modulePathIgnorePatterns: ["Example"],
setupFiles: [path.join(__dirname, "./jest/preamble.js")]
};
{
"name": "react-native-formik",
"version": "1.6.0",
"version": "1.7.0",
"description": "Make the most of your React Native forms with Formik",

@@ -37,5 +37,7 @@ "main": "index.js",

"devDependencies": {
"@babel/runtime": "^7.2.0",
"@types/react": "^16.3.8",
"babel-core": "6.26.0",
"babel-jest": "^22.4.1",
"babel-jest": "23.6.0",
"metro-react-native-babel-preset": "0.50.0",
"babel-preset-react-native": "4.0.0",

@@ -51,3 +53,3 @@ "commitizen": "^2.9.6",

"husky": "^0.14.3",
"jest": "^22.4.2",
"jest": "23.6.0",
"prettier": "^1.14.0",

@@ -54,0 +56,0 @@ "react": "16.2.0",

+240
-169

@@ -9,22 +9,36 @@ # React Native Formik [![Coverage Status](https://coveralls.io/repos/github/bamlab/react-native-formik/badge.svg?branch=master)](https://coveralls.io/github/bamlab/react-native-formik?branch=master) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release) [![NPM downloads](https://img.shields.io/npm/dm/react-native-formik.svg)](https://www.npmjs.com/package/react-native-formik) [![NPM downloads](https://img.shields.io/npm/dt/react-native-formik.svg)](https://www.npmjs.com/package/react-native-formik)

* Easily composable set of helpers
* Connects your React Native input to Formik with no boilerplate (See `makeReactNativeField`)
* Add a `type` prop on your TextInput to take care of the input options based on the type (See `withInputTypeProps`)
* Automatically focus the next input (See `withNextInputAutoFocus`)
- Easily composable set of helpers
- Connects your React Native input to Formik with no boilerplate (See `handleTextInput`)
- Add a `type` prop on your TextInput to take care of the input options based on the type (See `withInputTypeProps`)
- Automatically focus the next input (See `withNextInputAutoFocus`)
- Component agnostic: Handle any other form component with any design with `withFormikControl`
The point is to make your forms easy to write and provide features your users will expect with code as small as:
```javascript
<MyInput label="Email" name="email" type="email" />
<MyInput label="Password" name="password" type="password" />
<Switch label="Accept terms and conditions" name="accepted" />
<DatePicker label="Birthday" name="birthday" />
<Button onPress={props.handleSubmit} title="SUBMIT" />
```
**Table of contents**
* [Installation](#installation)
* [Advanced Example](#advanced-example)
* [Formatting inputs](#formatting-inputs)
* [API](#api)
* [makeReactNativeField](#makereactnativefield)
* [setFormikInitialValue](#setFormikInitialValue)
* [withError](#witherror)
* [withFocus](#withfocus)
* [withFormik](#withformik)
* [withInputTypeProps](#withinputtypeprops)
* [withNextInputAutoFocus](#withnextinputautofocus)
* [withTouched](#withtouched)
* [withPickerValues](#withpickervalues)
- [Installation](#installation)
- [Guides](#guides)
- [The Gist](#the-gist)
- [Custom components](#custom-components)
- [Formatting inputs](#formatting-inputs)
- [Move form above keyboard](#move-form-above-keyboard)
- [Step by step formik + react-native-formik integration](./doc/formik_step_by_step.md)
- [API](#api)
- [handleTextInput](#handleTextInput)
- [withErrorIfNeeded](#withErrorIfNeeded)
- [withError](#witherror)
- [withFocus](#withfocus)
- [withInputTypeProps](#withinputtypeprops)
- [withNextInputAutoFocus](#withnextinputautofocus)
- [withTouched](#withtouched)
- [withPickerValues](#withpickervalues)

@@ -37,7 +51,9 @@ ## Installation

## Advanced Example
## Guides
### The Gist
Say we want to create a form with Material design inputs.
### Create a custom input
#### Use any Input component

@@ -48,61 +64,27 @@ Let's create our custom text input design, called `MaterialTextInput`:

Our component takes `error` and `touched` in addition to the usual `TextInput` props.
Notice our component also implement a `focus` function, for `withNextInputAutoFocus` to work.
Our component will receive `error` in addition to the usual `TextInput` props.
For `withNextInputAutoFocus` to work, the input component should be a class and implement a `focus` method.
```javascript
// MaterialTextInput.js
import React from 'react';
import { Text, View } from 'react-native';
import { TextField } from 'react-native-material-textfield';
#### Create our form logic
export default class MaterialTextInput extends React.PureComponent {
// Your custom input needs a focus function for `withNextInputAutoFocus` to work
focus() {
this.input.focus();
}
We can compose our input with `handleTextInput` to make it boilerplate free. It will:
render() {
const { error, touched, ...props } = this.props;
- automatically manage its state in formik provided it has a `name` prop
- automatically set its error prop if input is touched or form has been submitted
- automatically
const displayError = !!error && touched;
const errorColor = 'rgb(239, 51, 64)';
return (
<View>
<TextField
ref={input => (this.input = input)}
labelHeight={12}
baseColor={displayError ? errorColor : '#1976D2'}
tintColor="#2196F3"
textColor="#212121"
{...props}
/>
<Text
style={{
textAlign: 'right',
color: displayError ? errorColor : 'transparent',
height: 20,
}}
>
{error}
</Text>
</View>
);
}
}
```
### Create our form logic
Compose our input with high order components to make it awesome.
`react-native-formik` exports as default `compose(withInputTypeProps, setFormikInitialValue, withError, withTouched, makeReactNativeField);`.
Let's add in `withNextInputAutoFocusInput`:
```javascript
import { compose } from 'recompose';
import makeInputGreatAgain, { withNextInputAutoFocusInput } from 'react-native-formik';
import MaterialTextInput from './MaterialTextInput';
import { compose } from "recompose";
import {
handleTextInput,
withNextInputAutoFocusInput
} from "react-native-formik";
import MaterialTextInput from "./MaterialTextInput";
const MyInput = compose(makeInputGreatAgain, withNextInputAutoFocusInput)(MaterialTextInput);
const MyInput = compose(
handleTextInput,
withNextInputAutoFocusInput
)(MaterialTextInput);
```

@@ -113,4 +95,4 @@

```javascript
import { View } from 'react-native';
import { withNextInputAutoFocusForm } from 'react-native-formik';
import { View } from "react-native";
import { withNextInputAutoFocusForm } from "react-native-formik";

@@ -123,3 +105,3 @@ const Form = withNextInputAutoFocusForm(View);

```javascript
import * as Yup from 'yup';
import * as Yup from "yup";

@@ -132,3 +114,3 @@ const validationSchema = Yup.object().shape({

.required()
.min(2, 'pretty sure this will be hacked'),
.min(2, "pretty sure this will be hacked")
});

@@ -162,14 +144,17 @@ ```

```javascript
import React from 'react';
import { Button, TextInput, View } from 'react-native';
import { compose } from 'recompose';
import { Formik } from 'formik';
import * as Yup from 'yup';
import React from "react";
import { Button, TextInput, View } from "react-native";
import { compose } from "recompose";
import { Formik } from "formik";
import * as Yup from "yup";
import makeInputGreatAgain, {
withNextInputAutoFocusForm,
withNextInputAutoFocusInput,
} from 'react-native-formik';
import MaterialTextInput from './MaterialTextInput';
withNextInputAutoFocusInput
} from "react-native-formik";
import MaterialTextInput from "./MaterialTextInput";
const MyInput = compose(makeInputGreatAgain, withNextInputAutoFocusInput)(MaterialTextInput);
const MyInput = compose(
makeInputGreatAgain,
withNextInputAutoFocusInput
)(MaterialTextInput);
const Form = withNextInputAutoFocusForm(View);

@@ -179,7 +164,7 @@

email: Yup.string()
.required('please! email?')
.required("please! email?")
.email("well that's not an email"),
password: Yup.string()
.required()
.min(2, 'pretty sure this will be hacked'),
.min(2, "pretty sure this will be hacked")
});

@@ -206,5 +191,60 @@

## Formatting inputs
Boilerplate-free, hassle-free, our form is awesome with minimum code required.
You may need to format inputs as the user types in. For instance, adding spaces in a telephone number (`0612345678` -> `06 12 34 56 78`).
### Custom components
#### withFormikControl usage
Thanks to `withFormikControl`, `formik` and `react-native-formik` can handle any custom component just like TextInputs, granted that the component takes as props:
```javascript
{
value: ValueType,
setFieldValue: (value: ValueType) => void,
error: ?string,
setFieldTouched: () => void
}
```
If you want to use `withNextInputAutoFocus`, your component should be a class and have a `focus` method.
Below is a simple example, a full example is available on `./src/Example/DatePicker.js`.
#### Simple Example: using a Switch
A very simple example would be handling a `Switch` component in your form:
```javascript
import React from "react";
import { Text, Switch as RNSwitch } from "react-native";
import { withFormikControl } from "react-native-formik";
class Switch extends React.PureComponent {
render() {
const { error, value, setFieldValue, label } = this.props;
return (
<React.Fragment>
<RNSwitch
value={value}
ios_backgroundColor={error ? "red" : "transparent"}
onValueChange={setFieldValue}
/>
<Text>{label}</Text>
</React.Fragment>
);
}
}
export default withFormikControl(Switch);
```
You can now use it in your form just like any other input:
```javascript
<Switch label="Accept terms and conditions" name="termsAndConditionsAccepted" />
```
### Formatting inputs
You may need to format inputs as the user types in. For instance, adding spaces in a telephone number (`0612345678` -> `06 12 34 56 78`).
Here's how you would do it:

@@ -228,81 +268,101 @@

## API
### Move form above keyboard
### makeReactNativeField
The purpose of this section is to give you a solution to create a bottom form which will go up when the keyboard appears, and the content at the top at the page will disappear.
Connects your React Native component to the Formik context:
You have to:
* its value will be set
* it will send `onChangeText` and `onBlur` events to Formik
- Create a form like you learnt above ;
- Use [react-native-keyboard-spacer](https://github.com/Andr3wHur5t/react-native-keyboard-spacer): it will create view with the keyboard's size when the keyboard will opened;
- Use [react-native-hide-with-keyboard](https://github.com/bamlab/react-native-hide-with-keyboard): it will hide component when the keyboard will opened.
Now you only need this code:
```javascript
import React from 'react';
import { TextInput, View } from 'react-native';
import { Formik } from 'formik';
import { makeReactNativeField } from 'react-native-formik';
import React, { PureComponent } from "react";
import { Image, Platform, ScrollView } from "react-native";
import Hide from "react-native-hide-with-keyboard";
import KeyboardSpacer from "react-native-keyboard-spacer";
import { Formik } from "formik";
import { Button, FormFormik, TextInputFormik } from "./components";
const cat = require("./cat.jpg");
const MyInput = makeReactNativeField(TextInput);
class AdoptACat extends PureComponent<{}> {
render() {
return (
<ScrollView
style={styles.container}
contentContainerStyle={styles.contentContainer}
keyboardShouldPersistTaps="handled"
>
<Hide>
<Image source={cat} style={styles.image} />
</Hide>
<View style={styles.fillContainer} />
<Formik
onSubmit={() => {}}
render={props => (
<FormFormik>
<TextInputFormik
name="catName"
placeholder={"His name"}
returnKeyType="next"
type="name"
/>
<TextInputFormik
name="humanName"
placeholder={"Your name"}
returnKeyType="done"
type="name"
/>
<Button text={"Adopt him ..."} />
</FormFormik>
)}
/>
{Platform.OS === "ios" && <KeyboardSpacer />}
</ScrollView>
);
}
}
export default props => {
return (
<Formik
onSubmit={values => console.log(values)}
render={props => {
return (
<View>
<MyInput name="email" />
<MyInput name="password" />
</View>
);
}}
/>
);
const styles = {
container: {
backgroundColor: "white",
flex: 1,
padding: 20
},
contentContainer: {
flex: 1
},
fillContainer: {
flex: 1
},
image: {
alignSelf: "center",
resizeMode: "contain"
}
};
export default AdoptACat;
```
instead of:
For Android, we don't have to use react-native-keyboard-spacer because `android:windowSoftInputMode` is in `adjustResize` mode. Indeed, the view is automatically resize and you don't have to fill it like on iOS.
```javascript
import React from 'react';
import { TextInput, View } from 'react-native';
import { Formik } from 'formik';
import { makeReactNativeField } from 'react-native-formik';
Enjoy your life :
const MyInput = makeReactNativeField(TextInput);
![iOS](https://github.com/bamlab/react-native-formik/blob/master/doc/images/bottomForm.gif)
export default props => {
return (
<Formik
onSubmit={values => console.log(values)}
render={props => {
return (
<View>
<MyInput
name="email"
value={props.value.email}
onChangeText={text => props.setFieldValue('email', text)}
onBlur={() => setFieldTouched('email')}
/>
<MyInput
name="password"
value={props.value.email}
onChangeText={text => props.setFieldValue('password', text)}
onBlur={() => setFieldTouched('password')}
/>
</View>
);
}}
/>
);
};
```
## API
### setFormikInitialValue
### withFormikControl
Set Input initial value to `""` to Formik without having to use `initialValues` prop.
See [usage](#Custom-components)
Especially it allows validation of untouched inputs when pressing submit.
### handleTextInput
A set of default HOC to manage TextInputs.
Includes `withErrorIfNeeded`, `withInputTypeProps` and `withFormikControl` remapped for specifically for the React Native `TextInput`
### withErrorIfNeeded
Pass in the Formik error for the input as a prop, only if input has been touched or the form has been submitted
### withError

@@ -316,6 +376,2 @@

### withFormik
Pass Formik context as a prop to any component.
### withInputTypeProps

@@ -328,4 +384,4 @@

```javascript
import { TextInput } from 'react-native';
import { withInputTypeProps } from 'react-native-formik';
import { TextInput } from "react-native";
import { withInputTypeProps } from "react-native-formik";

@@ -343,10 +399,13 @@ const MyInput = withInputTypeProps(TextInput);

* when an input is submitted, it will automatically focuses on the next or submit the form if it's the last one
* sets return key to "next" or "done" if input is the last one or not
* :warning: your input component needs to be a class and needs to implement a `focus` function
* :warning: Inputs need to be wrapped by `withNextInputAutoFocusInput` and the container of the inputs need to be wrapped in `withNextInputAutoFocusForm`.
- when an input is submitted, it will automatically focuses on the next or submit the form if it's the last one
- sets return key to "next" or "done" if input is the last one or not
- :warning: your input component needs to be a class and needs to implement a `focus` function
- :warning: Inputs need to be wrapped by `withNextInputAutoFocusInput` and the container of the inputs need to be wrapped in `withNextInputAutoFocusForm`.
```javascript
import { TextInput, View } from 'react-native';
import { withNextInputAutoFocusForm, withNextInputAutoFocusInput } from 'react-native-formik';
import { TextInput, View } from "react-native";
import {
withNextInputAutoFocusForm,
withNextInputAutoFocusInput
} from "react-native-formik";

@@ -385,11 +444,20 @@ const MyInput = withNextInputAutoFocusInput(TextInput);

```javascript
import { TextInput, View } from 'react-native';
import { TextInput, View } from "react-native";
import { compose } from "recompose";
import makeInput, { KeyboardModal, withPickerValues } from 'react-native-formik';
import makeInput, {
KeyboardModal,
withPickerValues
} from "react-native-formik";
const MyPicker = compose(makeInput, withPickerValues)(TextInput);
const MyPicker = compose(
makeInput,
withPickerValues
)(TextInput);
export default props => (
<Formik
onSubmit={values => { KeyboardModal.dismiss(); console.log(values); }}
onSubmit={values => {
KeyboardModal.dismiss();
console.log(values);
}}
validationSchema={validationSchema}

@@ -401,3 +469,6 @@ render={props => {

name="gender"
values={[{ label: 'male', value: 'Mr' }, { label: 'female', value: 'Mrs' }]}
values={[
{ label: "male", value: "Mr" },
{ label: "female", value: "Mrs" }
]}
/>

@@ -419,5 +490,5 @@ </View>

* Create a form like you learnt above ;
* Use [react-native-keyboard-spacer](https://github.com/Andr3wHur5t/react-native-keyboard-spacer): it will create view with the keyboard's size when the keyboard will opened;
* Use [react-native-hide-with-keyboard](https://github.com/bamlab/react-native-hide-with-keyboard): it will hide component when the keyboard will opened.
- Create a form like you learnt above ;
- Use [react-native-keyboard-spacer](https://github.com/Andr3wHur5t/react-native-keyboard-spacer): it will create view with the keyboard's size when the keyboard will opened;
- Use [react-native-hide-with-keyboard](https://github.com/bamlab/react-native-hide-with-keyboard): it will hide component when the keyboard will opened.

@@ -424,0 +495,0 @@ ```javascript

@@ -33,3 +33,5 @@ import React from "react";

const emailInput = mount(<Input name="email" />);
expect(emailInput.find(TextInput).props().value).toEqual("contact@bam.tech");
expect(emailInput.find(TextInput).props().value).toEqual(
"contact@bam.tech"
);
const otherInput = mount(<Input name="other" />);

@@ -36,0 +38,0 @@ expect(otherInput.find(TextInput).props().value).toEqual(undefined);

@@ -27,3 +27,5 @@ import React from "react";

const erroredInput = mount(<Input name="email" />);
expect(erroredInput.find(TextInput).props().error).toEqual("This is not a valid email.");
expect(erroredInput.find(TextInput).props().error).toEqual(
"This is not a valid email."
);
const validInput = mount(<Input name="valid" />);

@@ -35,3 +37,5 @@ expect(validInput.find(TextInput).props().error).toBeFalsy();

const emailInput = mount(<Input name="user.password" />);
expect(emailInput.find(TextInput).props().error).toEqual("Password is too short!");
expect(emailInput.find(TextInput).props().error).toEqual(
"Password is too short!"
);
const otherInput = mount(<Input name="user.username" />);

@@ -38,0 +42,0 @@ expect(otherInput.find(TextInput).props().error).toEqual(undefined);

@@ -22,5 +22,9 @@ import React from "react";

expect(withType.find(TextInput).props().someProp).toEqual("someValue");
const withUnknownType = mount(<Input type="unknown" someProp="someValue" />);
expect(withUnknownType.find(TextInput).props().someProp).toEqual("someValue");
const withUnknownType = mount(
<Input type="unknown" someProp="someValue" />
);
expect(withUnknownType.find(TextInput).props().someProp).toEqual(
"someValue"
);
});
});

@@ -68,3 +68,7 @@ import React from "react";

<Form>
<Input name="first" returnKeyType="correct value" onSubmitEditing={onSubmitEditing} />
<Input
name="first"
returnKeyType="correct value"
onSubmitEditing={onSubmitEditing}
/>
</Form>

@@ -71,0 +75,0 @@ );

@@ -8,19 +8,25 @@ import { compose, mapProps } from "recompose";

withFormik,
mapProps(({ formik: { setFieldValue, setFieldTouched, values, isSubmitting }, name, ...props }) => ({
value: _.get(values, name),
...props,
name,
onChangeText: text => {
setFieldValue(name, text);
if (props.onChangeText) props.onChangeText(text);
},
onBlur: () => {
// validate onBlur only while not submitting
// this prevents validating twice in succession when clicking 'done' on keyboard - first onSubmitEditing, then onBlur
setFieldTouched(name, true, !isSubmitting);
if (props.onBlur) props.onBlur();
}
}))
mapProps(
({
formik: { setFieldValue, setFieldTouched, values, isSubmitting },
name,
...props
}) => ({
value: _.get(values, name),
...props,
name,
onChangeText: text => {
setFieldValue(name, text);
if (props.onChangeText) props.onChangeText(text);
},
onBlur: () => {
// validate onBlur only while not submitting
// this prevents validating twice in succession when clicking 'done' on keyboard - first onSubmitEditing, then onBlur
setFieldTouched(name, true, !isSubmitting);
if (props.onBlur) props.onBlur();
}
})
)
);
export default makeReactNativeField;

@@ -20,3 +20,10 @@ import React from "react";

render() {
return <WrappedInput focused={this.state.focused} {...this.props} onBlur={this.onBlur} onFocus={this.onFocus} />;
return (
<WrappedInput
focused={this.state.focused}
{...this.props}
onBlur={this.onBlur}
onFocus={this.onFocus}
/>
);
}

@@ -23,0 +30,0 @@ };

@@ -17,7 +17,11 @@ import React from "react";

}
if (child && child.props && !!child.props.name) return partialInputs.concat(child);
if (child && child.props && !!child.props.name)
return partialInputs.concat(child);
return partialInputs;
}, []);
export const withNextInputAutoFocusForm = (WrappedComponent, { submitAfterLastInput } = { submitAfterLastInput: true }) => {
export const withNextInputAutoFocusForm = (
WrappedComponent,
{ submitAfterLastInput } = { submitAfterLastInput: true }
) => {
class WithNextInputAutoFocusForm extends React.PureComponent {

@@ -36,3 +40,4 @@ static childContextTypes = withNextInputAutoFocusContextType;

getInputPosition = name => this.inputs.findIndex(input => input.props.name === name);
getInputPosition = name =>
this.inputs.findIndex(input => input.props.name === name);

@@ -45,12 +50,13 @@ getChildContext = () => ({

const inputPosition = this.getInputPosition(name);
const isLastInput = inputPosition === this.inputs.length - 1;
const nextInputs = this.inputs.slice(inputPosition + 1);
const nextFocusableInput = nextInputs.find(
element =>
this.inputRefs[element.props.name] &&
this.inputRefs[element.props.name].focus
);
if (isLastInput) {
if (nextFocusableInput) {
this.inputRefs[nextFocusableInput.props.name].focus();
} else {
if (submitAfterLastInput) this.props.formik.submitForm();
} else {
const nextInputs = this.inputs.slice(inputPosition + 1);
const nextFocusableInput = nextInputs.find(
element => this.inputRefs[element.props.name] && this.inputRefs[element.props.name].focus
);
this.inputRefs[nextFocusableInput.props.name].focus();
}

@@ -75,3 +81,6 @@ },

export const withNextInputAutoFocusInput = Input => {
class WithNextInputAutoFocusInput extends React.Component<$FlowFixMeProps, $FlowFixMeState> {
class WithNextInputAutoFocusInput extends React.Component<
$FlowFixMeProps,
$FlowFixMeState
> {
static contextTypes = withNextInputAutoFocusContextType;

@@ -78,0 +87,0 @@

@@ -20,3 +20,6 @@ // @flow

isOpen={open}
style={[{ height: 216, backgroundColor: "rgb(200, 203, 211)" }, props.style]}
style={[
{ height: 216, backgroundColor: "rgb(200, 203, 211)" },
props.style
]}
easing={props.easingAnimation}

@@ -36,3 +39,4 @@ >

if (open) currentProps = props;
if (keyboardModalInstance) keyboardModalInstance.update(renderModal(props, open));
if (keyboardModalInstance)
keyboardModalInstance.update(renderModal(props, open));
};

@@ -59,3 +63,6 @@

keyboardModalInstance = new RootSiblings(renderModal(props));
keyboardDidShowListener = Keyboard.addListener("keyboardWillShow", keyboardDidShow);
keyboardDidShowListener = Keyboard.addListener(
"keyboardWillShow",
keyboardDidShow
);
};

@@ -62,0 +69,0 @@

@@ -35,3 +35,7 @@ // @flow

const picker = (
<Picker onValueChange={this.onValueChange} selectedValue={value} prompt={placeholder}>
<Picker
onValueChange={this.onValueChange}
selectedValue={value}
prompt={placeholder}
>
{values.map(item => (

@@ -70,3 +74,5 @@ <Picker.Item key={item.value} {...item} />

<View>
<DisableKeyboard onPress={this.openPicker}>{this.props.children}</DisableKeyboard>
<DisableKeyboard onPress={this.openPicker}>
{this.props.children}
</DisableKeyboard>
{this.renderPicker()}

@@ -73,0 +79,0 @@ </View>

@@ -7,11 +7,19 @@ // @flow

const withPickerModal = Component => {
class WithPickerModal extends React.Component<$FlowFixMeProps, $FlowFixMeState> {
class WithPickerModal extends React.Component<
$FlowFixMeProps,
$FlowFixMeState
> {
render() {
const selectedItem =
this.props.values && this.props.values.length > 0
? this.props.values.find(item => item && item.value === this.props.value)
? this.props.values.find(
item => item && item.value === this.props.value
)
: undefined;
return (
<PickerModal {...this.props}>
<Component {...this.props} value={selectedItem ? selectedItem.label : ""} />
<Component
{...this.props}
value={selectedItem ? selectedItem.label : ""}
/>
</PickerModal>

@@ -18,0 +26,0 @@ );

Sorry, the diff of this file is too big to display