Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 40 additions & 41 deletions src/CheckSome.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, {createContext} from 'react';
import React, {createContext, useState} from 'react';
import isEqual from 'lodash/isEqual';
import CheckSomeField from './Field';
import {ValidationErrors} from './globals';
Expand All @@ -15,47 +15,36 @@ export type CheckSomeChildProps<T> = {
errors: ValidationGroupErrors<T>;
};

export type CheckSomeProps<T> = {
interface CheckSomeOptions<T> {
rules: ValidationGroupRules<T>;
values: ValidationGroupValues<T>;
initialValues?: ValidationGroupValues<T>;
}

export interface CheckSomeProps<T> extends CheckSomeOptions<T> {
children: (props: CheckSomeChildProps<T>) => React.ReactNode;
};
}

export const CheckSomeContext = createContext<{
values: {[key: string]: any};
errors: {[key: string]: ValidationErrors | undefined} | null;
}>({values: {}, errors: {}});

export default class CheckSome<T> extends React.Component<CheckSomeProps<T>> {
static Field = CheckSomeField;

static defaultProps = {
rules: {},
};

initialValues: Object | null | undefined;
export const useValidation = <T extends {}>({
rules,
values,
initialValues,
}: CheckSomeOptions<T>) => {
const [compareAgainst] = useState(initialValues || values);

getInitialValues = () => {
if (this.props.initialValues) {
return this.props.initialValues;
}

if (!this.initialValues) {
this.initialValues = this.props.values;
}

return this.initialValues;
};

getErrors = (): ValidationGroupErrors<T> =>
Object.keys(this.props.rules).reduce((errors: ValidationGroupErrors<T>, keyValue) => {
const errors: ValidationGroupErrors<T> = Object.keys(rules).reduce(
(errors: ValidationGroupErrors<T>, keyValue) => {
// @ts-ignore
const key: keyof T = keyValue;
const rules = this.props.rules[key];
const value = this.props.values[key];
const ruleList = rules[key];
const value = values[key];

const newErrors = rules!.reduce(
const newErrors = ruleList!.reduce(
(e: ValidationErrors | null, rule: ValidationRule<T[typeof key]>) => e || rule(value),
null,
);
Expand All @@ -71,20 +60,30 @@ export default class CheckSome<T> extends React.Component<CheckSomeProps<T>> {
errors[key] = newErrors;

return errors;
}, null);
},
null,
);

render() {
const {values} = this.props;
const valid = !errors;
const changed = !isEqual(values, compareAgainst);

const errors = this.getErrors();
const valid = !errors;
return {valid, errors, changed};
};

const changed = !isEqual(values, this.getInitialValues());
export const CheckSome = <T extends {}>({
rules,
values,
initialValues,
children,
}: CheckSomeProps<T>) => {
const {valid, errors, changed} = useValidation({rules, values, initialValues});

return (
<CheckSomeContext.Provider value={{values, errors}}>
{children({valid, errors, changed})}
</CheckSomeContext.Provider>
);
};
CheckSome.Field = CheckSomeField;

return (
<CheckSomeContext.Provider value={{values, errors}}>
{this.props.children({valid, errors, changed})}
</CheckSomeContext.Provider>
);
}
}
export default CheckSome;
1 change: 1 addition & 0 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import CheckSome from './CheckSome';
export {useValidation} from './CheckSome';

export default CheckSome;
59 changes: 58 additions & 1 deletion test/CheckSome.test.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, {useState} from 'react';
import CheckSome from '../src';
import CheckSome, {useValidation} from '../src';
import {render, fireEvent} from '@testing-library/react';

const required = value => (value || value === 0 ? null : {required: {}});
Expand All @@ -24,6 +24,63 @@ const TestField = ({label, value, onValueChanged, errors, valid, touched}) => (
</div>
);

const TestFormWithHooks = ({values, rules, initialValues = undefined}) => {
const [requiredStringValue, setRequiredStringValue] = useState(values.requiredString);
const [numberValue, setNumberValue] = useState(values.testNumber);
const [optionalStringValue, setOptionalStringValue] = useState(values.optionalString);

const {valid, changed, errors} = useValidation({
values: {
requiredString: requiredStringValue,
testNumber: numberValue,
optionalString: optionalStringValue,
},
rules,
initialValues,
});

return (
<form>
<div>Form {valid ? 'Valid' : 'Invalid'}</div>
<div>{changed ? 'Changed' : 'Unchanged'}</div>
<div>Form Errors: {JSON.stringify(errors)}</div>

<CheckSome.Field name="requiredString">
{fieldProps => (
<TestField
label="Required String"
value={requiredStringValue}
onValueChanged={setRequiredStringValue}
{...fieldProps}
/>
)}
</CheckSome.Field>

<CheckSome.Field name="testNumber">
{fieldProps => (
<TestField
label="Test Number"
value={numberValue.toString()}
onValueChanged={v => setNumberValue(Number.parseInt(v))}
{...fieldProps}
/>
)}
</CheckSome.Field>

<CheckSome.Field name="optionalString">
{fieldProps => (
<TestField
label="Optional String"
value={optionalStringValue}
onValueChanged={setOptionalStringValue}
{...fieldProps}
/>
)}
</CheckSome.Field>
</form>
);
};

const TestForm = ({values, rules, initialValues = undefined}) => {
const [requiredStringValue, setRequiredStringValue] = useState(values.requiredString);
const [numberValue, setNumberValue] = useState(values.testNumber);
Expand Down