Skip to content

Commit

Permalink
Merge pull request #31 from pedro-lb/30-pass-label-from-schema-to-the…
Browse files Browse the repository at this point in the history
…-custom-input-props

30 pass label from schema to the custom input props
  • Loading branch information
pedro-lb authored Jul 15, 2020
2 parents 136108f + 3f23762 commit 3fb5c72
Show file tree
Hide file tree
Showing 12 changed files with 184 additions and 81 deletions.
10 changes: 8 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
language: node_js

node_js:
- 9
- 8
- 12

before_install:
- cd packages/core

script:
- yarn build
20 changes: 12 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Formup integrates Formik with Yup, reducing the code overhead needed to create y

It provides the best of both worlds for all of your forms so you can create, initialize and validate any form in only a few lines of code 📝💯.

Of course, you'll still have all validation options and functionality from Yup and all helpers from Formik. Formup is essentialy a bridge between these two libraries so that you can work easily without worrying about writing any middleware.
Of course, you'll still have all validation options and functionality from Yup and all helpers from Formik. Formup is essentially a bridge between these two libraries so that you can work easily without worrying about writing any middleware.

## Online Example

Expand Down Expand Up @@ -90,13 +90,17 @@ const MyComponent = () => {
return (
<Form formikForm={formikForm}>
{/*
FormInput will take care of all validation!
Simply provide the "name" prop.
FormInput will take care of all validation and property mapping!
Properties such as "label" will be automatically inherited from your
schema, but you can override them by passing the prop to FormInput.
You simply need to provide the "name" prop.
*/}

<FormInput name="name" label="Name" />
<FormInput name="email" label="Email" />
<FormInput name="age" label="Age" />
<FormInput name="name" />
<FormInput name="email" />
<FormInput name="age" label="Custom Age Label" />

<button type="button" className="form-button" onClick={submitForm}>
Submit!
Expand Down Expand Up @@ -223,7 +227,7 @@ const {
} = useFormup(...);
<Form formikForm={formikForm}>
<FormInputGroup name="favouriteFood" multi initialValue={['Oreo', 'Pie']}>
<FormInputGroup name="favoriteFood" multi initialValue={['Oreo', 'Pie']}>
<p>What's your gender?</p>
<FormInputGroupItem value="Ice Cream Sandwich" component={Checkbox} />
Expand Down Expand Up @@ -292,7 +296,7 @@ You can choose to validate the whole form (which formup does by default), or:
- Validate an array of objects (nested fields)
- Validate fields + objects

To do this, you simply need to pass `validationOptions.path` to formup's `submitForm` options:
To do this, you simply need to pass `validationOptions.path` to formup `submitForm` options:

```tsx
const {
Expand Down
16 changes: 7 additions & 9 deletions example/src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,8 @@ const schema = createSchema({
.label('Accepted terms'),

// You can control single choice fields using Form Groups
favouriteFood: yup.string()
.label('Favourite Food'),
favoriteFood: yup.string()
.label('Favorite Food'),

// And even multi-level nested fields!
authentication: yup.object().shape({
Expand All @@ -90,7 +90,7 @@ const App = () => {
setSubmissionResult(`Form validation errors! \n ${JSON.stringify(errors, null, 2)}`);
};

// Here we'll handle submiting the form
// Here we'll handle submitting the form
const handleSubmitForm = (values) => {
setSubmissionResult(`Form is valid! \n ${JSON.stringify(values, null, 2)}`);

Expand Down Expand Up @@ -142,7 +142,7 @@ const App = () => {
Simply provide the <strong>name</strong> prop to link with your schema field.
</Typography>

<FormInput name="name" label="Name" />
<FormInput name="name" />

<Typography variant="h5" align="left" className={classes.marginTop5}>
<span role="img" aria-label="Check"></span>
Expand Down Expand Up @@ -216,15 +216,13 @@ const App = () => {
<FormInput
type="password"
name="authentication.password"
label="Password"
component={TextFieldWithErrorMessage}
injectFormupData
/>

<FormInput
type="password"
name="authentication.confirmPassword"
label="Confirm Password"
component={TextFieldWithErrorMessage}
injectFormupData
/>
Expand Down Expand Up @@ -298,9 +296,9 @@ const App = () => {
to set the initial checked items?
</Typography>

<FormInputGroup name="favouriteFood" multi initialValue={['Cupcake', 'Donut']}>
<FormInputGroup name="favoriteFood" multi initialValue={['Cupcake', 'Donut']}>
<Typography variant="body2" align="left" className={classes.marginTop2}>
What's favourite food? You can pick as many as you want!
What's favorite food? You can pick as many as you want!
</Typography>

<FormControlLabel
Expand Down Expand Up @@ -360,7 +358,7 @@ const App = () => {
</Typography>

<Typography variant="body1" align="left" className={classes.subtitle}>
Here's your fancy form value in a beaultiful real-time JSON string.
Here's your fancy form value in a beautiful real-time JSON string.
</Typography>

<TextField
Expand Down
3 changes: 3 additions & 0 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
},
"scripts": {
"lint": "./node_modules/.bin/eslint ./src --ext .js,.jsx,.ts,.tsx",
"prebuild": "yarn run lint",
"build": "rollup -c",
"copy_publish_files": "cp ../../README.md .",
"prepublish": "yarn run build && yarn run copy_publish_files"
Expand All @@ -26,6 +27,7 @@
"formik": "2.1.3",
"immutability-helper": "^3.0.2",
"invariant": "^2.2.4",
"lodash.get": "^4.4.2",
"merge": "^1.2.1",
"scheduler": "^0.18.0",
"yup": "0.28.1"
Expand All @@ -40,6 +42,7 @@
"@types/classnames": "^2.2.9",
"@types/invariant": "^2.2.33",
"@types/jest": "^23.1.5",
"@types/lodash.get": "^4.4.6",
"@types/react": "^16.9.19",
"@types/react-dom": "^16.0.5",
"@types/yup": "^0.26.29",
Expand Down
65 changes: 8 additions & 57 deletions packages/core/src/components/FormInput/FormInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,71 +4,17 @@ import invariant from 'invariant';

import { FORMUP_INPUT_CLASS_NAME, FORMUP_INPUT_DANGER_CLASS_NAME } from '../../constants/identifiers';
import DefaultInputComponent from '../DefaultInputComponents/DefaultInputComponent';
import { ExtendedFormupFormInputData, FormInputProps } from '../../interfaces';
import { useFormContext } from '../../contexts/FormContext/FormContext';
import checkFormInputError from '../../utils/checkFormInputError';
import { ExtendedFormupFormInputData } from '../../interfaces';
import composeInputEvent from '../../utils/composeInputEvent';
import extractEventValue from '../../utils/extractEventValue';

export interface FormInputComponentProps extends React.Props<any> {
/**
* Boolean indicating if the component has validation errors.
*
* This is injected by default to maintain compatibility with the
* major React libraries such as Material UI, React Bootstrap, etc.
*
* Thanks to this formup will be compatible out-of-the box with those
* dependencies, making the invalid inputs automatically styled, with
* red borders around it and such.
*/
error: boolean;

/**
* This is an object that contains Formup extended information, such
* as the validation error message for this input (if any).
*
* Due to compatibility issues, this will only be injected if the prop
* "injectFormupData" is defined as true in <FormInput /> component.
*
* Remember that this shouldn't be injected into the final <input />
* component, in order to avoid React errors.
*/
formupData?: ExtendedFormupFormInputData;
}

export interface FormInputProps extends React.Props<any> {
component: React.ElementType<FormInputComponentProps>;
name: string;
id?: string;
type?: string;
value?: any;
defaultValue?: any;
children?: React.ReactChild;
onBlur?: (arg0: React.FormEvent<HTMLInputElement>) => void;
onChange?: (arg0: React.FormEvent<HTMLInputElement>) => void;
onKeyPress?: (arg0: React.FormEvent<HTMLInputElement>) => void;
className?: any,

/**
* Defines if the "formupData" prop will be injected into the
* <input /> component that is being rendered (default by formup),
* or the component passed into "component" prop on FormInput.
*
* This is false by default in order to avoid compatibility issues,
* since when true the component will have to deal with "formupData"
* in order to avoid injecting it into the final <input /> component.
*
* If "formupData" is injected into the final <input /> component,
* React will throw an error in the console saying that "formupData"
* is not a valid property for <input />. Refer to the docs for more information.
*/
injectFormupData: boolean;
}
import getFieldLabel from '../../utils/getFieldLabel';

/**
* Input that auto-validates itself within the form.
*
* Can be ovewritten with the "component" prop,
* Can be overwritten with the "component" prop,
* allowing you to render any type of component
* while still maintaining all validation functionality.
*
Expand All @@ -85,6 +31,7 @@ const FormInput = ({
children,
onChange,
onBlur,
label,
name,
...props
}: FormInputProps) => {
Expand All @@ -103,9 +50,13 @@ const FormInput = ({

const formInputError = checkFormInputError(formInputMeta);

const inputLabel = label ?? getFieldLabel(name, form?.schema);

const inputProps = {
...props,
...formInputProps,
label: inputLabel,
'aria-label': inputLabel,
id: props?.id || name,
onChange: (event: any) => {
const newValue = extractEventValue(event);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export interface FormInputGroupItemProps extends React.Props<any> {
* You need to pass in "value" (value of this distinct input)
* in order to correctly render and use this component.
*
* Can be ovewritten with the "component" prop,
* Can be overwritten with the "component" prop,
* allowing you to render any type of component
* while still maintaining all validation functionality.
* @param param0 Options.
Expand Down
76 changes: 75 additions & 1 deletion packages/core/src/interfaces/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import {

import { FormInputGroupItemProps } from '../components/FormInputGroupItem/FormInputGroupItem';
import { FormInputGroupProps } from '../components/FormInputGroup/FormInputGroup';
import { FormInputProps } from '../components/FormInput/FormInput';
import { FormProps } from '../components/Form/Form';

/**
Expand Down Expand Up @@ -55,6 +54,81 @@ export interface ValidateFormResult extends yup.ValidateOptions {
error?: FormupValidationError | undefined;
}

/**
* Interface to define component properties inherited by FormInput's component.
*/
export interface FormInputComponentProps extends React.Props<any> {
/**
* Boolean indicating if the component has validation errors.
*
* This is injected by default to maintain compatibility with the
* major React libraries such as Material UI, React Bootstrap, etc.
*
* Thanks to this formup will be compatible out-of-the box with those
* dependencies, making the invalid inputs automatically styled, with
* red borders around it and such.
*/
error: boolean;

/**
* Input's label, inherited from the schema definition, or from
* FormInput's "label" property.
*/
label: string;

/**
* This is an object that contains Formup extended information, such
* as the validation error message for this input (if any).
*
* Due to compatibility issues, this will only be injected if the prop
* "injectFormupData" is defined as true in <FormInput /> component.
*
* Remember that this shouldn't be injected into the final <input />
* component, in order to avoid React errors.
*/
formupData?: ExtendedFormupFormInputData;
}

/**
* Interface to define FormInput component properties.
*/
export interface FormInputProps extends React.Props<any> {
component: React.ElementType<FormInputComponentProps>;
name: string;
id?: string;
type?: string;
value?: any;
defaultValue?: any;
children?: React.ReactChild;
onBlur?: (arg0: React.FormEvent<HTMLInputElement>) => void;
onChange?: (arg0: React.FormEvent<HTMLInputElement>) => void;
onKeyPress?: (arg0: React.FormEvent<HTMLInputElement>) => void;
className?: any;

/**
* Component label.
*
* Will be automatically inherited from the schema (if defined on the field),
* but you can still override it by passing this property to FormInput.
*/
label?: string;

/**
* Defines if the "formupData" prop will be injected into the
* <input /> component that is being rendered (default by formup),
* or the component passed into "component" prop on FormInput.
*
* This is false by default in order to avoid compatibility issues,
* since when true the component will have to deal with "formupData"
* in order to avoid injecting it into the final <input /> component.
*
* If "formupData" is injected into the final <input /> component,
* React will throw an error in the console saying that "formupData"
* is not a valid property for <input />. Refer to the docs for more information.
*/
injectFormupData: boolean;
}

/**
* Options for useFormup.
*/
Expand Down
18 changes: 18 additions & 0 deletions packages/core/src/utils/getFieldLabel.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { FormupYupSchema } from '../interfaces';
import getSchemaField from './getSchemaField';

/**
* If defined, extracts the field label from the schema.
* @param name The field name
* @param schema The schema
*/
const getFieldLabel = (
name: string,
schema: FormupYupSchema,
) => {
const schemaField = getSchemaField(name, schema);

return schemaField?.['_label'];
};

export default getFieldLabel;
23 changes: 23 additions & 0 deletions packages/core/src/utils/getSchemaField.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import get from 'lodash.get';

import { FormupYupSchema } from '../interfaces';

/**
* Recursively gets one schema field according to its name.
* @param name The field name
* @param schema The schema
*/
const getSchemaField = (
name: string,
schema: FormupYupSchema,
) => {
const fieldPath = String(name || '')
.split('.')
.reduce((prev, curr) => `${prev}.fields.${curr}`);

const schemaField = get(schema?.fields || {}, fieldPath);

return schemaField;
};

export default getSchemaField;
Loading

0 comments on commit 3fb5c72

Please sign in to comment.