Skip to content

Commit

Permalink
Allow validation errors to be React elements (#394)
Browse files Browse the repository at this point in the history
  • Loading branch information
rkuykendall authored Feb 17, 2020
1 parent bebefbe commit 4096296
Show file tree
Hide file tree
Showing 3 changed files with 34 additions and 26 deletions.
52 changes: 29 additions & 23 deletions src/Wrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,19 @@ import PropTypes from 'prop-types';

import * as utils from './utils';
import FormsyContext from './FormsyContext';
import { ComponentWithStaticAttributes, RequiredValidation, Validations, WrappedComponentClass } from './interfaces';
import {
ComponentWithStaticAttributes,
RequiredValidation,
ValidationError,
Validations,
WrappedComponentClass,
} from './interfaces';
import { isString } from './utils';

/* eslint-disable react/default-props-match-prop-types */

const convertValidationsToObject = <V>(validations: false | Validations<V>): Validations<V> => {
if (typeof validations === 'string') {
if (isString(validations)) {
return validations.split(/,(?![^{[]*[}\]])/g).reduce((validationsAccumulator, validation) => {
let args: string[] = validation.split(':');
const validateMethod: string = args.shift();
Expand Down Expand Up @@ -49,8 +56,8 @@ export interface WrapperProps<V> {
innerRef?: (ref: React.Ref<any>) => void;
name: string;
required?: RequiredValidation<V>;
validationError?: string;
validationErrors?: { [key: string]: string };
validationError?: ValidationError;
validationErrors?: { [key: string]: ValidationError };
validations?: Validations<V>;
value?: V;
}
Expand All @@ -63,13 +70,13 @@ export interface WrapperState<V> {
isRequired: boolean;
isValid: boolean;
pristineValue: V;
validationError: string[];
validationError: ValidationError[];
value: V;
}

export interface InjectedProps<V> {
errorMessage: string;
errorMessages: string[];
errorMessage: ValidationError;
errorMessages: ValidationError[];
hasValue: boolean;
isFormDisabled: boolean;
isFormSubmitted: boolean;
Expand All @@ -85,10 +92,13 @@ export interface InjectedProps<V> {
showRequired: boolean;
}

export interface WrapperInstanceMethods {
export interface WrapperInstanceMethods<V> {
getErrorMessage: () => null | ValidationError;
getErrorMessages: () => ValidationError[];
getValue: () => V;
isFormDisabled: () => boolean;
isValid: () => boolean;
getValue: () => any;
getErrorMessage: () => null | string;
setValue: (value: V) => void;
}

export type PassDownProps<V> = WrapperProps<V> & InjectedProps<V>;
Expand All @@ -102,7 +112,7 @@ function getDisplayName(component: WrappedComponentClass) {
export default function<T, V>(
WrappedComponent: React.ComponentType<T & PassDownProps<V>>,
): React.ComponentType<Omit<T & WrapperProps<V>, keyof InjectedProps<V>>> {
return class extends React.Component<T & WrapperProps<V>, WrapperState<V>> implements WrapperInstanceMethods {
return class extends React.Component<T & WrapperProps<V>, WrapperState<V>> implements WrapperInstanceMethods<V> {
// eslint-disable-next-line react/sort-comp
public static contextType = FormsyContext;

Expand Down Expand Up @@ -155,11 +165,10 @@ export default function<T, V>(

public shouldComponentUpdate(nextProps, nextState, nextContext) {
const { props, state, context } = this;
const isPropsChanged = Object.keys(props).some(k => props[k] !== nextProps[k]);

const isStateChanged = Object.keys(state).some(k => state[k] !== nextState[k]);

const isFormsyContextChanged = Object.keys(context).some(k => context[k] !== nextContext[k]);
const isChanged = (a: object, b: object): boolean => Object.keys(a).some(k => a[k] !== b[k]);
const isPropsChanged = isChanged(props, nextProps);
const isStateChanged = isChanged(state, nextState);
const isFormsyContextChanged = isChanged(context, nextContext);

return isPropsChanged || isStateChanged || isFormsyContextChanged;
}
Expand All @@ -182,18 +191,17 @@ export default function<T, V>(
}

// Detach it when component unmounts
// eslint-disable-next-line react/sort-comp
public componentWillUnmount() {
const { detachFromForm } = this.context;
detachFromForm(this);
}

public getErrorMessage = () => {
public getErrorMessage = (): ValidationError | null => {
const messages = this.getErrorMessages();
return messages.length ? messages[0] : null;
};

public getErrorMessages = () => {
public getErrorMessages = (): ValidationError[] => {
const { externalError, validationError } = this.state;

if (!this.isValid() || this.showRequired()) {
Expand All @@ -218,9 +226,7 @@ export default function<T, V>(
const { validate: validateForm } = this.context;

if (!validate) {
this.setState({
value,
});
this.setState({ value });
} else {
this.setState(
{
Expand All @@ -237,7 +243,7 @@ export default function<T, V>(
// eslint-disable-next-line react/destructuring-assignment
public hasValue = () => {
const { value } = this.state;
if (typeof value === 'string') {
if (isString(value)) {
return value !== '';
}
return value !== undefined;
Expand Down
4 changes: 3 additions & 1 deletion src/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ export type IResetModel = (model?: IModel) => void;
export type IUpdateInputsWithValue<V> = (values: { [key: string]: V }, validate?: boolean) => void;
export type IUpdateInputsWithError = (errors: { [key: string]: string }, invalidate?: boolean) => void;

export type ValidationFunction<V> = (values: Values, value: V, extra?: any) => boolean | string;
export type ValidationError = string | React.ReactNode;

export type ValidationFunction<V> = (values: Values, value: V, extra?: any) => boolean | ValidationError;

export type Validation<V> = string | boolean | ValidationFunction<V>;

Expand Down
4 changes: 2 additions & 2 deletions src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import isPlainObject from 'lodash.isplainobject';

import { Validations, Values } from './interfaces';
import { ValidationError, Validations, Values } from './interfaces';

export function isArray(value: unknown): value is unknown[] {
return Array.isArray(value);
Expand Down Expand Up @@ -86,7 +86,7 @@ export function isSame(a: unknown, b: unknown) {
}

interface RulesResult {
errors: string[];
errors: ValidationError[];
failed: string[];
success: string[];
}
Expand Down

0 comments on commit 4096296

Please sign in to comment.