Skip to content

Commit

Permalink
Merge pull request #44 from ArrayKnight/feat/value-transform
Browse files Browse the repository at this point in the history
Feat/value transform
  • Loading branch information
selbekk authored Apr 24, 2019
2 parents bda731c + a0ceb9f commit d2a0bd8
Show file tree
Hide file tree
Showing 6 changed files with 92 additions and 72 deletions.
75 changes: 21 additions & 54 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -399,65 +399,13 @@ When you have a simple form to validate.
#### Props
##### `children: func.isRequired`
The `children` function is called with an object with the following props:
```js
{
dirty: object, // Object with all fields isDirty state, keyed per field
errors: object, // object with the same keys as `fields`, but with error messages
fields: object, // object with the form field values, to make controlled components
resetAll: func, // call this to programmatically trigger a full state reset
setError: func, // callback accepting a diff object, updating errors like setState
setField: func, // callback accepting a diff object, updating fields like setState
submit: func, // call this to programmatically trigger a submitted state
submitted: bool, // flag showing whether the form has been submitted once or not
}
```
The `setField` function is used whenever you want to update a field outside of
a typical `change` event. Pass an object with the diff you want to apply (like
React's `setState`), and it will update and reevaluate your form.
##### `config: object.isRequired`
The config object specifies what you want to validate, and which validators to
apply to it.
Each validator can accept an object with a `message` key or - in the case where
you don't have to specify anything other than a validation message - just a
string with the error message.
##### `initialValues: object`
The `initialValues` object lets you specify the initial values of the form
fields. These values are available from the `fields` argument in the `children`
function, which lets you control your form fields.
##### `onSubmit: func`
This callback is fired whenever the form is submitted. That can happen whenever
somebody clicks the submit button, or hits `enter` in the form.
The `onSubmit` function is called with an object with the following props:
```js
{
dirty: object, // Object with all fields isDirty state, keyed per field
errors: object, // Object with all error messages, keyed per field
fields: object, // Object with all field inputs, keyed per field
isValid: bool, // Boolean indicating whether your form is valid or not
resetAll: func, // call this to programmatically trigger a full state reset
setError: func, // callback accepting a diff object, updating errors like setState
}
```
Accepts all of the props from `Form` and `Validation` below.
### `Form`
`import { Form } from 'calidators';`
When you want to wrap a complex form (in conjunction )
When you want to wrap a complex form (in conjunction)
#### Props
Expand Down Expand Up @@ -522,6 +470,25 @@ Each validator can accept an object with a `message` key or - in the case where
you don't have to specify anything other than a validation message - just a
string with the error message.
##### `initialValues: object`
The `initialValues` object lets you specify the initial values of the form
fields. These values are available from the `fields` argument in the `children`
function, which lets you control your form fields.
The `transforms` object lets you apply transforms to the value before it
is stored and validated against. Each key should map to a field name and be a function
that receives and returns a value. This is useful if you wish to convert a value's
type or enforce casing.
```js
{
foo: (value) => parseInt(value),
bar: (value) => !!value ? 'YES' : 'NO',
etc: (value) => value.toLowerCase(),
}
```
### `ValidatorsProvider`
`import { ValidatorsProvider } from 'calidators';`
Expand Down
25 changes: 17 additions & 8 deletions src/Form.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,20 +33,24 @@ class Form extends Component {
};

initialValues = {};
transforms = {};

onChange = e => {
this.props.onChange(e);

if (!this.state.config[e.target.name]) {
const { checked, name, type, value } = e.target;

if (!this.state.config[name]) {
return;
}

this.setField({
[e.target.name]:
e.target.type === 'checkbox'
? e.target.checked
: e.target.value,
});
let val = type === 'checkbox' ? checked : value;

if (typeof this.transforms[name] === 'function') {
val = this.transforms[name](val);
}

this.setField({ [name]: val });
};

onReset = e => {
Expand Down Expand Up @@ -177,11 +181,15 @@ class Form extends Component {
null,
);

registerSubComponent = (subComponentConfig, initialValues) => {
registerSubComponent = (subComponentConfig, transforms, initialValues) => {
this.initialValues = {
...this.initialValues,
...initialValues,
};
this.transforms = {
...this.transforms,
...transforms,
};

this.setState(prevState => {
const config = {
Expand All @@ -205,6 +213,7 @@ class Form extends Component {
const keys = Object.keys(subComponentConfig);

this.initialValues = removeFrom(this.initialValues)(keys);
this.transforms = removeFrom(this.transforms)(keys);

this.setState(prevState => {
const config = removeFrom(prevState.config)(keys);
Expand Down
8 changes: 6 additions & 2 deletions src/FormValidation.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,15 @@ import Form from './Form';
import Validation from './Validation';

const FormValidation = props => {
const { children, config, initialValues, ...rest } = props;
const { children, config, initialValues, transforms, ...rest } = props;

return (
<Form {...rest}>
<Validation config={config} initialValues={initialValues}>
<Validation
config={config}
initialValues={initialValues}
transforms={transforms}
>
{children}
</Validation>
</Form>
Expand Down
22 changes: 15 additions & 7 deletions src/Validation.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ const propTypes = {
children: func.isRequired,
config: shape({}).isRequired,
initialValues: shape({}),
transforms: shape({}),
};

class Validation extends Component {
static defaultProps = {
errors: {},
fields: {},
initialValues: {},
transforms: {},
};

static propTypes = {
Expand All @@ -37,17 +39,23 @@ class Validation extends Component {
};

componentDidMount() {
const { register, initialValues, config } = this.props;
const { config, initialValues, register, transforms } = this.props;

register(
config,
Object.keys(config).reduce(
(allFields, field) => ({
transforms,
Object.keys(config).reduce((allFields, field) => {
let value = getFirstDefinedValue(initialValues[field], '');

if (typeof transforms[field] === 'function') {
value = transforms[field](value);
}

return {
...allFields,
[field]: getFirstDefinedValue(initialValues[field], ''),
}),
{},
),
[field]: value,
};
}, {}),
);

this.setState({ isRegistered: true });
Expand Down
32 changes: 32 additions & 0 deletions src/__tests__/integration-tests.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,38 @@ describe('<FormValidation />', () => {
expect(getByLabelText(/optional/i).value).toBe('');
expect(queryByTestId('optional-error')).toBeNull();
});

it('transforms values (initial & after change)', () => {
const initialValues = { username: 'foo', optional: '77' };
const { getByLabelText, queryByTestId } = render(
<FormValidation
config={exampleConfig}
initialValues={initialValues}
transforms={{
username: value => value.toUpperCase(),
optional: value => (parseInt(value) < 100 ? '0' : '1'),
}}
>
{props => <ExampleForm {...props} />}
</FormValidation>,
);

expect(getByLabelText(/username/i).value).toBe('FOO');

fireEvent.change(getByLabelText(/username/i), {
target: { name: 'username', value: 'bar' },
});

expect(getByLabelText(/username/i).value).toBe('BAR');

expect(getByLabelText(/optional/i).value).toBe('0');

fireEvent.change(getByLabelText(/optional/i), {
target: { name: 'optional', value: '109' },
});

expect(getByLabelText(/optional/i).value).toBe('1');
});
});

describe('<ValidatorsProvider />', () => {
Expand Down
2 changes: 1 addition & 1 deletion src/__tests__/utilities.spec.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { areDirty, getFirstDefinedValue, removeFrom, uuid } from '../utilities';
import { areDirty, getFirstDefinedValue, removeFrom } from '../utilities';

const foo = 'foo';
const bar = 'bar';
Expand Down

0 comments on commit d2a0bd8

Please sign in to comment.