diff --git a/README.md b/README.md
index de90f29..e5b2791 100644
--- a/README.md
+++ b/README.md
@@ -6,26 +6,27 @@ This repository is a set of high order components designed to help you take cont
**Features**
-- 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`)
-- Awesome Test coverage [](https://coveralls.io/github/bamlab/react-native-formik?branch=master)
+* 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`)
+* Awesome Test coverage [](https://coveralls.io/github/bamlab/react-native-formik?branch=master)
**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)
+* [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
@@ -38,6 +39,7 @@ yarn add react-native-formik
Say we want to create a form with Material design inputs.
### Create a custom input
+
Let's create our custom text input design, called `MaterialTextInput`:
We can use [react-native-material-textfield](https://github.com/n4kz/react-native-material-textfield) for the material design.
@@ -47,9 +49,9 @@ Notice our component also implement a `focus` function, for `withNextInputAutoFo
```javascript
// MaterialTextInput.js
-import React from "react";
-import { Text, View } from "react-native";
-import { TextField } from "react-native-material-textfield";
+import React from 'react';
+import { Text, View } from 'react-native';
+import { TextField } from 'react-native-material-textfield';
export default class MaterialTextInput extends React.PureComponent {
// Your custom input needs a focus function for `withNextInputAutoFocus` to work
@@ -61,19 +63,25 @@ export default class MaterialTextInput extends React.PureComponent {
const { error, touched, ...props } = this.props;
const displayError = !!error && touched;
- const errorColor = "rgb(239, 51, 64)";
+ const errorColor = 'rgb(239, 51, 64)';
return (
this.input = input}
+ ref={input => (this.input = input)}
labelHeight={12}
- baseColor={displayError ? errorColor : "#1976D2"}
+ baseColor={displayError ? errorColor : '#1976D2'}
tintColor="#2196F3"
textColor="#212121"
{...props}
/>
-
+
{error}
@@ -90,9 +98,9 @@ Compose our input with high order components to make it awesome.
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 makeInputGreatAgain, { withNextInputAutoFocusInput } from 'react-native-formik';
+import MaterialTextInput from './MaterialTextInput';
const MyInput = compose(makeInputGreatAgain, withNextInputAutoFocusInput)(MaterialTextInput);
```
@@ -100,8 +108,8 @@ const MyInput = compose(makeInputGreatAgain, withNextInputAutoFocusInput)(Materi
To complement `withNextInputAutoFocusInput`, we need to create a `Form` component, for instance:
```javascript
-import { View } from "react-native";
-import { withNextInputAutoFocusForm } from "react-native-formik";
+import { View } from 'react-native';
+import { withNextInputAutoFocusForm } from 'react-native-formik';
const Form = withNextInputAutoFocusForm(View);
```
@@ -109,7 +117,7 @@ const Form = withNextInputAutoFocusForm(View);
We can also create a validation schema, with `yup`. It's of course possible to use other validation possibilities provided by Formik, but `yup` makes validation and error messaging painless.
```javascript
-import Yup from "yup";
+import Yup from 'yup';
const validationSchema = Yup.object().shape({
email: Yup.string()
@@ -117,7 +125,7 @@ const validationSchema = Yup.object().shape({
.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'),
});
```
@@ -126,7 +134,7 @@ Then the form in itself becomes simple:
```javascript
export default props => (
console.log(values)}
+ onSubmit={values => console.log(values)}
validationSchema={validationSchema}
render={props => {
return (
@@ -146,30 +154,32 @@ export default props => (
Full code:
```javascript
-import React from "react";
-import { Button, TextInput, View } from "react-native";
-import { compose } from "recompose"
-import { Formik } from "formik";
-import Yup from "yup";
-import makeInputGreatAgain, { withNextInputAutoFocusForm, withNextInputAutoFocusInput } from "react-native-formik";
-import MaterialTextInput from "./MaterialTextInput";
+import React from 'react';
+import { Button, TextInput, View } from 'react-native';
+import { compose } from 'recompose';
+import { Formik } from 'formik';
+import Yup from 'yup';
+import makeInputGreatAgain, {
+ withNextInputAutoFocusForm,
+ withNextInputAutoFocusInput,
+} from 'react-native-formik';
+import MaterialTextInput from './MaterialTextInput';
const MyInput = compose(makeInputGreatAgain, withNextInputAutoFocusInput)(MaterialTextInput);
const Form = withNextInputAutoFocusForm(View);
const validationSchema = Yup.object().shape({
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'),
});
-
export default props => (
console.log(values)}
+ onSubmit={values => console.log(values)}
validationSchema={validationSchema}
render={props => {
return (
@@ -212,22 +222,24 @@ const formatPhoneNumber: string => string = (unformattedPhoneNumber) => ...;
### makeReactNativeField
Connects your React Native component to the Formik context:
-- its value will be set
-- it will send `onChangeText` and `onBlur` events to Formik
+
+* its value will be set
+* it will send `onChangeText` and `onBlur` events to Formik
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 from 'react';
+import { TextInput, View } from 'react-native';
+import { Formik } from 'formik';
+import { makeReactNativeField } from 'react-native-formik';
const MyInput = makeReactNativeField(TextInput);
export default props => {
return (
console.log(values)}
+ onSubmit={values => console.log(values)}
render={props => {
return (
@@ -242,32 +254,33 @@ export default props => {
```
instead of:
+
```javascript
-import React from "react";
-import { TextInput, View } from "react-native";
-import { Formik } from "formik";
-import { makeReactNativeField } from "react-native-formik";
+import React from 'react';
+import { TextInput, View } from 'react-native';
+import { Formik } from 'formik';
+import { makeReactNativeField } from 'react-native-formik';
const MyInput = makeReactNativeField(TextInput);
export default props => {
return (
console.log(values)}
+ onSubmit={values => console.log(values)}
render={props => {
return (
props.setFieldValue("email", text)}
- onBlur={() => setFieldTouched("email")}
+ onChangeText={text => props.setFieldValue('email', text)}
+ onBlur={() => setFieldTouched('email')}
/>
props.setFieldValue("password", text)}
- onBlur={() => setFieldTouched("password")}
+ onChangeText={text => props.setFieldValue('password', text)}
+ onBlur={() => setFieldTouched('password')}
/>
);
@@ -302,8 +315,8 @@ Let's face it, you'll always want to remove auto-capitalization for email inputs
Using `withInputTypeProps` and passing a `type`, you'll always get the correct props for you input.
```javascript
-import { TextInput } from "react-native";
-import { withInputTypeProps } from "react-native-formik";
+import { TextInput } from 'react-native';
+import { withInputTypeProps } from 'react-native-formik';
const MyInput = withInputTypeProps(TextInput);
@@ -316,21 +329,21 @@ Check [the props set by the type](./src/withInputTypeProps) in the source!
### withNextInputAutoFocus
-- 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';
const MyInput = withNextInputAutoFocusInput(TextInput);
const Form = withNextInputAutoFocusForm(View);
export default props => (
console.log(values)}
+ onSubmit={values => console.log(values)}
validationSchema={validationSchema}
render={props => {
return (
@@ -348,3 +361,32 @@ export default props => (
### withTouched
Pass in the Formik touched value for the input as a prop.
+
+### withPickerValues
+
+Wraps your component into a `TouchableOpacity` which, when pressed, opens a dialog to pick a value.
+You need to provide a `values` props with the pickable items.
+
+```javascript
+import { TextInput, View } from 'react-native';
+import { withPickerValues } from 'react-native-formik';
+
+const MyPicker = withPickerValues(TextInput);
+
+export default props => (
+ console.log(values)}
+ validationSchema={validationSchema}
+ render={props => {
+ return (
+
+
+
+ );
+ }}
+ />
+);
+```
diff --git a/index.js b/index.js
index 346cebf..0e73233 100644
--- a/index.js
+++ b/index.js
@@ -1,14 +1,24 @@
-import { compose } from "recompose";
-import setFormikInitialValue from "./src/setFormikInitialValue";
-import withError from "./src/withError";
-import withFocus from "./src/withFocus";
-import withFormik from "./src/withFormik";
-import withInputTypeProps from "./src/withInputTypeProps";
-import withTouched from "./src/withTouched";
-import makeReactNativeField from "./src/makeReactNativeField";
-import { withNextInputAutoFocusForm, withNextInputAutoFocusInput } from "./src/withNextInputAutoFocus";
+import { compose } from 'recompose';
+import setFormikInitialValue from './src/setFormikInitialValue';
+import withError from './src/withError';
+import withFocus from './src/withFocus';
+import withFormik from './src/withFormik';
+import withInputTypeProps from './src/withInputTypeProps';
+import withTouched from './src/withTouched';
+import withPickerValues from './src/withPickerValues';
+import makeReactNativeField from './src/makeReactNativeField';
+import {
+ withNextInputAutoFocusForm,
+ withNextInputAutoFocusInput,
+} from './src/withNextInputAutoFocus';
-const makeInputsGreatAgain = compose(withInputTypeProps, setFormikInitialValue, withError, withTouched, makeReactNativeField);
+const makeInputsGreatAgain = compose(
+ withInputTypeProps,
+ setFormikInitialValue,
+ withError,
+ withTouched,
+ makeReactNativeField
+);
export default makeInputsGreatAgain;
@@ -22,5 +32,6 @@ export {
withInputTypeProps,
withTouched,
withNextInputAutoFocusForm,
- withNextInputAutoFocusInput
+ withNextInputAutoFocusInput,
+ withPickerValues,
};
diff --git a/jest.config.js b/jest.config.js
index 2e6709f..cf80afd 100644
--- a/jest.config.js
+++ b/jest.config.js
@@ -1,6 +1,9 @@
-const path = require("path");
+const path = require('path');
module.exports = {
- preset: "react-native",
- setupFiles: [path.join(__dirname, "./jest/preamble.js")]
+ 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/)',
+ ],
+ setupFiles: [path.join(__dirname, './jest/preamble.js')],
};
diff --git a/package.json b/package.json
index 6b6ab74..0a06d75 100644
--- a/package.json
+++ b/package.json
@@ -21,6 +21,8 @@
"forms"
],
"dependencies": {
+ "@bam.tech/react-native-modalbox": "^1.4.2",
+ "@bam.tech/react-native-root-siblings": "^2.0.3",
"lodash": "4.17.5",
"recompose": "^0.26.0"
},
diff --git a/src/withPickerValues/DisableKeyboard.js b/src/withPickerValues/DisableKeyboard.js
new file mode 100644
index 0000000..28a39a6
--- /dev/null
+++ b/src/withPickerValues/DisableKeyboard.js
@@ -0,0 +1,26 @@
+// @flow
+
+import React, { PureComponent } from 'react';
+import { TouchableOpacity, View } from 'react-native';
+
+type _Props = {
+ children: ?*,
+ onPress: () => any,
+ children: any,
+};
+
+export default class DisableInputKeyboard extends PureComponent {
+ static defaultProps = {
+ children: null,
+ };
+
+ props: _Props;
+
+ render() {
+ return (
+
+ {this.props.children}
+
+ );
+ }
+}
diff --git a/src/withPickerValues/KeyboardModal.js b/src/withPickerValues/KeyboardModal.js
new file mode 100644
index 0000000..332c38d
--- /dev/null
+++ b/src/withPickerValues/KeyboardModal.js
@@ -0,0 +1,111 @@
+// @flow
+
+import React, { PureComponent } from 'react';
+import { Easing, Keyboard } from 'react-native';
+import Modal from '@bam.tech/react-native-modalbox';
+import RootSiblings from '@bam.tech/react-native-root-siblings';
+
+type _Props = {
+ style: Object,
+ easingAnimation: () => void,
+ children: any,
+};
+
+const renderModal = (props: _Props, open: ?boolean) => (
+
+ {props.children}
+
+);
+
+let keyboardModalInstance = null;
+let displayedKeyboardComponent = null;
+let keyboardModalCount = 0;
+let keyboardDidShowListener = null;
+let currentProps;
+
+const updateKeyboardModalComponent = (props: _Props, open: ?boolean) => {
+ if (open) currentProps = props;
+ if (keyboardModalInstance) keyboardModalInstance.update(renderModal(props, open));
+};
+
+const open = (keyboardComponent: any) => {
+ if (displayedKeyboardComponent) displayedKeyboardComponent.displayed = false;
+
+ displayedKeyboardComponent = keyboardComponent;
+ displayedKeyboardComponent.displayed = true;
+
+ updateKeyboardModalComponent(keyboardComponent.props, true);
+};
+
+const keyboardDidShow = () => updateKeyboardModalComponent(currentProps, false);
+
+const createKeyboardModalComponent = (props: _Props) => {
+ keyboardModalCount += 1;
+
+ if (keyboardModalCount > 1) return;
+
+ currentProps = props;
+
+ keyboardModalInstance = new RootSiblings(renderModal(props));
+ debugger;
+ keyboardDidShowListener = Keyboard.addListener('keyboardWillShow', keyboardDidShow);
+};
+
+const removeKeyboardModalComponent = () => {
+ keyboardModalCount -= 1;
+
+ if (keyboardModalCount === 0) {
+ if (keyboardDidShowListener) keyboardDidShowListener.remove();
+ if (keyboardModalInstance) keyboardModalInstance.remove();
+ }
+};
+
+export default class KeyboardModal extends PureComponent {
+ static dismiss() {
+ if (keyboardModalCount > 0) {
+ updateKeyboardModalComponent(currentProps, false);
+ }
+ }
+
+ static defaultProps = {
+ easingAnimation: Easing.ease,
+ };
+
+ componentWillMount() {
+ createKeyboardModalComponent(this.props);
+ }
+
+ componentWillReceiveProps(nextProps: _Props) {
+ if (this.displayed) {
+ updateKeyboardModalComponent(nextProps);
+ }
+ }
+
+ componentWillUnmount() {
+ removeKeyboardModalComponent();
+ }
+
+ displayed: boolean = false;
+
+ open() {
+ this.displayed = true;
+ Keyboard.dismiss();
+ open(this);
+ }
+
+ close() {
+ this.displayed = false;
+ updateKeyboardModalComponent(this.props, false);
+ }
+
+ render() {
+ return null;
+ }
+}
diff --git a/src/withPickerValues/PickerModal.js b/src/withPickerValues/PickerModal.js
new file mode 100644
index 0000000..9449e0d
--- /dev/null
+++ b/src/withPickerValues/PickerModal.js
@@ -0,0 +1,54 @@
+// @flow
+import React, { PureComponent } from 'react';
+import { View, Platform, Picker } from 'react-native';
+
+import KeyboardModal from './KeyboardModal';
+import DisableKeyboard from './DisableKeyboard';
+
+type PropsType = {
+ values: Array<{ label: string, value: string }>,
+};
+
+class PickerModal extends PureComponent {
+ pickerModal: ?KeyboardModal;
+
+ openPicker = () => {
+ if (this.pickerModal) this.pickerModal.open();
+ };
+
+ onValueChange = (value: any) => {
+ if (this.props.onChangeText) this.props.onChangeText(value);
+ };
+
+ renderPicker = () => {
+ const picker = (
+
+
+ {this.props.values.map(item => )}
+
+ );
+
+ return Platform.OS === 'ios' ? (
+ {
+ this.pickerModal = ref;
+ }}
+ >
+ {picker}
+
+ ) : (
+ picker
+ );
+ };
+
+ render() {
+ return (
+
+ {this.props.children}
+ {this.renderPicker()}
+
+ );
+ }
+}
+
+export default PickerModal;
diff --git a/src/withPickerValues/index.js b/src/withPickerValues/index.js
new file mode 100644
index 0000000..39e6c7d
--- /dev/null
+++ b/src/withPickerValues/index.js
@@ -0,0 +1 @@
+export { default } from './withPickerValues';
diff --git a/src/withPickerValues/withPickerValues.js b/src/withPickerValues/withPickerValues.js
new file mode 100644
index 0000000..ab1a52b
--- /dev/null
+++ b/src/withPickerValues/withPickerValues.js
@@ -0,0 +1,17 @@
+// @flow
+
+import React from 'react';
+import { compose } from 'recompose';
+
+import PickerModal from './PickerModal';
+
+const withPickerModal = Component => props => {
+ const selectedItem = props.values.find(item => item.value === props.value);
+ return (
+
+
+
+ );
+};
+
+export default compose(withPickerModal);
diff --git a/yarn.lock b/yarn.lock
index 72071af..f98de8b 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -16,6 +16,20 @@
esutils "^2.0.2"
js-tokens "^3.0.0"
+"@bam.tech/react-native-modalbox@^1.4.2":
+ version "1.4.2"
+ resolved "https://registry.yarnpkg.com/@bam.tech/react-native-modalbox/-/react-native-modalbox-1.4.2.tgz#85d130358354834bccb74f5a8d0384e8b5491e29"
+ dependencies:
+ create-react-class "^15.6.0"
+ prop-types "^15.5.10"
+
+"@bam.tech/react-native-root-siblings@^2.0.3":
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/@bam.tech/react-native-root-siblings/-/react-native-root-siblings-2.0.3.tgz#ac1dee579874998b2796ad20f6648d7033320c5c"
+ dependencies:
+ fbemitter "^2.1.1"
+ static-container "^1.0.0"
+
"@mrmlnc/readdir-enhanced@^2.2.1":
version "2.2.1"
resolved "https://registry.yarnpkg.com/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz#524af240d1a360527b730475ecfa1344aa540dde"
@@ -1811,7 +1825,7 @@ create-error-class@^3.0.0:
dependencies:
capture-stack-trace "^1.0.0"
-create-react-class@^15.5.2:
+create-react-class@^15.5.2, create-react-class@^15.6.0:
version "15.6.3"
resolved "https://registry.yarnpkg.com/create-react-class/-/create-react-class-15.6.3.tgz#2d73237fb3f970ae6ebe011a9e66f46dbca80036"
dependencies:
@@ -2522,6 +2536,12 @@ fb-watchman@^2.0.0:
dependencies:
bser "^2.0.0"
+fbemitter@^2.1.1:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/fbemitter/-/fbemitter-2.1.1.tgz#523e14fdaf5248805bb02f62efc33be703f51865"
+ dependencies:
+ fbjs "^0.8.4"
+
fbjs-scripts@^0.8.1:
version "0.8.1"
resolved "https://registry.yarnpkg.com/fbjs-scripts/-/fbjs-scripts-0.8.1.tgz#c1c6efbecb7f008478468976b783880c2f669765"
@@ -2535,7 +2555,7 @@ fbjs-scripts@^0.8.1:
semver "^5.1.0"
through2 "^2.0.0"
-fbjs@^0.8.1, fbjs@^0.8.14, fbjs@^0.8.16, fbjs@^0.8.9:
+fbjs@^0.8.1, fbjs@^0.8.14, fbjs@^0.8.16, fbjs@^0.8.4, fbjs@^0.8.9:
version "0.8.16"
resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.16.tgz#5e67432f550dc41b572bf55847b8aca64e5337db"
dependencies:
@@ -5348,7 +5368,7 @@ promise@^7.1.1:
dependencies:
asap "~2.0.3"
-prop-types@^15.5.8, prop-types@^15.6.0:
+prop-types@^15.5.10, prop-types@^15.5.8, prop-types@^15.6.0:
version "15.6.1"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.1.tgz#36644453564255ddda391191fb3a125cbdf654ca"
dependencies:
@@ -6342,6 +6362,10 @@ stacktrace-parser@^0.1.3:
version "0.1.4"
resolved "https://registry.yarnpkg.com/stacktrace-parser/-/stacktrace-parser-0.1.4.tgz#01397922e5f62ecf30845522c95c4fe1d25e7d4e"
+static-container@^1.0.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/static-container/-/static-container-1.2.0.tgz#1c6e92b869da47d32abe47f45f50bf39661d9c2f"
+
static-extend@^0.1.1:
version "0.1.2"
resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6"