Skip to content

Commit ad5066a

Browse files
tpucciAlmouro
authored andcommitted
feat(withFormikControl): Add standard use of withFormikControl
1 parent e0d2442 commit ad5066a

File tree

4 files changed

+254
-0
lines changed

4 files changed

+254
-0
lines changed

README.md

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,15 @@ This repository is a set of high order components designed to help you take cont
77
**Features**
88

99
* Easily composable set of helpers
10+
* Easily connect your [React Native Formik Standard Component](./doc/react-native-formik-standard-component.md) to the formik context
1011
* Connects your React Native input to Formik with no boilerplate (See `makeReactNativeField`)
1112
* Add a `type` prop on your TextInput to take care of the input options based on the type (See `withInputTypeProps`)
1213
* Automatically focus the next input (See `withNextInputAutoFocus`)
1314

1415
**Table of contents**
1516

1617
* [Installation](#installation)
18+
* [The Gist](#the-gist)
1719
* [Advanced Example](#advanced-example)
1820
* [Formatting inputs](#formatting-inputs)
1921
* [API](#api)
@@ -33,6 +35,118 @@ This repository is a set of high order components designed to help you take cont
3335
yarn add formik react-native-formik
3436
```
3537

38+
## The Gist (with common use cases)
39+
40+
Once you designed `FocusableStandardRNFormikFieldComponent` and `StandardRNFormikFieldComponent` which respects the [React Native Formik Standard Component](./doc/react-native-formik-standard-component.md), here is an example to use those fields.
41+
42+
```js
43+
// lib/Form.js
44+
```
45+
```js
46+
import { compose } from 'recompose';
47+
import { View } from 'react-native';
48+
import {
49+
withFormikControl,
50+
withNextInputAutoFocusForm,
51+
withNextInputAutoFocusInput,
52+
} from 'react-native-formik';
53+
54+
import FocusableStandardRNFormikFieldComponent from 'app/src/components/FocusableStandardRNFormikField';
55+
56+
import StandardRNFormikFieldComponent from 'app/src/components/StandardRNFormikField';
57+
58+
const Container = withNextInputAutoFocusForm(View);
59+
const FocusableStandardRNFormikField = compose(
60+
withNextInputAutoFocusInput,
61+
withFormikControl(FocusableStandardRNFormikFieldComponent),
62+
);
63+
const StandardRNFormikField = withFormikControl(StandardRNFormikFieldComponent);
64+
65+
export default {
66+
Container,
67+
FocusableStandardRNFormikField,
68+
StandardRNFormikField,
69+
};
70+
```
71+
```js
72+
// pages/one_form.js
73+
```
74+
```js
75+
import React, { Component } from 'react';
76+
import { View, StyleSheet, TouchableHighlight, Text } from 'react-native';
77+
import { Formik } from 'formik';
78+
import * as Yup from 'yup';
79+
import Form from 'app/src/lib/Form';
80+
81+
class OneForm extends Component {
82+
validationSchema = Yup.object().shape({
83+
firstNumber: Yup.number().required('required!'),
84+
secondNumber: Yup.number().required('required!'),
85+
comments: Yup.string().when('secondNumber', {
86+
is: OneForm.shouldDisplayCommentsField,
87+
then: Yup.string().required('required because second number is less than 5!'),
88+
}), // Dynamic validation
89+
email: Yup.string()
90+
.required('required mate!')
91+
.email("This is not an email")
92+
.test(
93+
'alreadyExists',
94+
"This email already exists in our database :(",
95+
value =>
96+
value &&
97+
new Promise(resolve => {
98+
setTimeout(() => {
99+
resolve(value && value.endsWith('exists'));
100+
}, 2000);
101+
}) // This asynchronouly validate the data
102+
),
103+
});
104+
105+
static shouldDisplayCommentsField = secondNumber => secondNumber && secondNumber < 5;
106+
107+
renderForm = ({ values, handleSubmit }) => (
108+
<Form.Container style={styles.page}>
109+
<Form.FocusableStandardRNFormikField name="email" />
110+
<Form.StandardRNFormikFieldComponent name="firstNumber" />
111+
<Form.StandardRNFormikFieldComponent name="secondNumber" />
112+
113+
{OneForm.shouldDisplayCommentsField(values.secondNumber) && (
114+
<Form.StandardRNFormikFieldComponent name="comments" />
115+
)} // Dynamic field rendering
116+
<TouchableHighlight onPress={handleSubmit}>
117+
<Text style={styles.submitButton}>OK</Text>
118+
</TouchableHighlight>
119+
</Form.Container>
120+
);
121+
122+
render() {
123+
return (
124+
<Formik
125+
onSubmit={values => console.log(values)}
126+
validationSchema={this.validationSchema}
127+
render={this.renderForm}
128+
/>
129+
);
130+
}
131+
}
132+
133+
const styles = StyleSheet.create({
134+
page: {
135+
backgroundColor: 'white',
136+
flex: 1,
137+
padding: 8,
138+
},
139+
submitButton: {
140+
backgroundColor: 'blue',
141+
textAlign: 'center',
142+
padding: 4,
143+
color: 'white',
144+
},
145+
});
146+
147+
export default OneForm;
148+
```
149+
36150
## Advanced Example
37151

38152
Say we want to create a form with Material design inputs.
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
# React Native Formik Standard Component
2+
3+
## Checks
4+
5+
- [ ] ⚛️ It is a react Stateless PureComponent
6+
- [ ] 🎯 Its `PropsType` include the following typing
7+
```js
8+
type StandardReactNativeFormikFieldComponentProps = {
9+
name: string,
10+
error?: string,
11+
value?: string | number,
12+
onChange: (value: string | number) => any,
13+
}
14+
```
15+
- [ ] ❔ (Optionnal) If you want it to work with React Native Formik `withNextInputAutoFocus` helper, it hhas a public `focus` method
16+
17+
## Good example
18+
19+
```js
20+
// @flow
21+
import React, { PureComponent } from 'react';
22+
import { Text, TouchableHighlight, StyleSheet, Keyboard } from 'react-native';
23+
import FieldContainer from './FieldContainer';
24+
25+
type Props = {
26+
label: string,
27+
error: string,
28+
data: Array<{ label: string, value: string | number }>,
29+
onChange: (value: string | number) => any,
30+
};
31+
32+
class MultiChoices extends PureComponent<Props> {
33+
focus = () => {
34+
Keyboard.dismiss();
35+
};
36+
37+
render() {
38+
return (
39+
<FieldContainer label={this.props.label} error={this.props.error}>
40+
{this.props.data.map(choice => (
41+
<Choice
42+
key={choice.value}
43+
onPress={this.props.onChange}
44+
label={choice.label}
45+
value={choice.value}
46+
selected={this.props.value === choice.value}
47+
/>
48+
))}
49+
</FieldContainer>
50+
);
51+
}
52+
}
53+
54+
type ChoiceProps = {
55+
label: string,
56+
value: string | number,
57+
onPress: (value: string | number) => any,
58+
selected: boolean,
59+
};
60+
61+
class Choice extends PureComponent<ChoiceProps> {
62+
onPress = () => {
63+
this.props.onPress(this.props.value);
64+
};
65+
66+
render() {
67+
return (
68+
<TouchableHighlight onPress={this.onPress}>
69+
<Text style={this.props.selected && styles.selectedChoice}>{this.props.label}</Text>
70+
</TouchableHighlight>
71+
);
72+
}
73+
}
74+
75+
type FieldContainerProps = {
76+
error: string,
77+
label: string,
78+
children: Node,
79+
};
80+
81+
class FieldContainer extends PureComponent<FieldContainerProps> {
82+
render() {
83+
return (
84+
<View style={styles.container}>
85+
<Text style={[styles.label, !!this.props.error && styles.error]}>{this.props.label}</Text>
86+
{!!this.props.error && (
87+
<Text style={[styles.errorLabel, styles.error]}>{this.props.error}</Text>
88+
)}
89+
{this.props.children}
90+
</View>
91+
);
92+
}
93+
}
94+
95+
const styles = StyleSheet.create({
96+
container: {
97+
marginBottom: 16,
98+
},
99+
label: {
100+
fontWeight: '500',
101+
marginBottom: 8,
102+
},
103+
selectedChoice: {
104+
backgroundColor: 'green',
105+
},
106+
error: {
107+
color: 'red',
108+
},
109+
errorLabel: {
110+
marginBottom: 8,
111+
},
112+
});
113+
114+
export default MultiChoices;
115+
```

index.d.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,12 @@ export interface withTouchedProps {
2020
name: string;
2121
}
2222

23+
export interface withFormikControlProps {
24+
error?: string;
25+
value: string;
26+
onChange: string;
27+
}
28+
2329
export type makeInputGreatAgainProps = makeReactNativeFieldProps &
2430
setFormikInitialValueProps &
2531
withInputTypePropsProps &
@@ -66,4 +72,8 @@ export function makeInputGreatAgain<Props>(
6672
WrappedComponent: React.ComponentType<Props>
6773
): React.ComponentClass<Props & makeInputGreatAgainProps>;
6874

75+
export function withFormikControl<Props>(
76+
WrappedComponent: React.ComponentType<Props>
77+
): React.ComponentClass<Props & makeInputGreatAgainProps>;
78+
6979
export default makeInputGreatAgain;

src/withFormikControl.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { withProps, compose } from "recompose";
2+
import { withFormik } from "./withFormik";
3+
4+
const withFormikControl = compose(
5+
withFormik,
6+
withProps(ownProps => ({
7+
error: ownProps.formik.submitCount
8+
? ownProps.formik.errors[ownProps.name]
9+
: undefined,
10+
value: ownProps.formik.values[ownProps.name],
11+
onChange: value => ownProps.formik.setFieldValue(ownProps.name, value)
12+
}))
13+
);
14+
15+
export default withFormikControl;

0 commit comments

Comments
 (0)