在React中使用HOC创建受控输入

问题描述

我试图创建一个HOC来呈现受控的输入,但是我的所有努力都导致onChange之后输入的焦点下降。我以为这是密钥的基本问题,但是摆弄密钥似乎无济于事,我花了几个小时进入各种“带有输入的HOC”博客/示例,似乎无法从父节点的输入中找到任何具有受控输入的东西。州。我究竟做错了什么?预先感谢!

编辑:HOC的目标是获取验证钩子。添加其他代码后,我为此问题创建了sandbox,希望能对您有所帮助。

    type WrapperProps = {
      name: string;
      label: string;
      onChange?: (event: any) => void;
      state: any;
    }
    
    export const InputWrapper = (v: ValidationObject) => {
      const Wrapper: FC<WrapperProps> = (props) => {
        const { label,onChange,name,state } = props;
    
        const getPattern = (value: any) => {
          return v.getFieldValid(name)
            ? `${value}`
            : `${randomString()}`
        };

        const modifiedProps = { 
          name,onBlur: () => v.validate(name,prop(name,state),pattern: getPattern(state[name]),value: state[name],};

        return (
          <React.Fragment>
            <label htmlFor={name}>{label}</label>
            <input key={name} id={name} {...modifiedProps} />
            <p style={{ color: 'red' }}>{v.getError(name)}</p>
          </React.Fragment>
        );
      }
 
     return Wrapper;
    }

我的验证钩,它将导出HOC的参数:

validation.hook.ts

    import { useState } from 'react';
    import { prop,map,all,indexOf,mergeDeepRight } from 'ramda';
    import { createValidationsstate,compose,isEqual } from 'util/utilities';
    import { InputWrapper } from './wrapper2';
    
    export interface ValidationArray<T> {
      key: keyof T;
      value: unkNown;
    }
    
    export interface ErrorMessages {
      [key: string]: string;
    }
    
    export interface ValidationFunction {
      (val: any,state: any): boolean | string | number;
    }
    
    // Dictionary of Booleans
    export interface ValidationState {
      [key: string]: {
        isValid: boolean;
        error: string;
      };
    }
    
    // Dictionary of validation deFinitions
    export interface ValidationProps {
      errorMessage: string;
      validation: ValidationFunction;
    }
    
    export interface ValidationSchema {
      [key: string]: ValidationProps[];
    }
    
    export interface ValidationObject {
      getError: Function;
      getFieldValid: Function;
      isValid: boolean;
      validate: Function;
      validateall: Function;
      validateIfTrue: Function;
      validationState: ValidationState;
    }
    
    /**
     * A hook that can be used to generate an object containing functions and
     * properties pertaining to the validation state provided.
     * @param validationSchema an object containing all the properties you want to validate
     * @returns object { getError,getFieldValid,isValid,validate,validateall,validateIfTrue,validationState }
     */
    export const useValidation = <S>(validationSchema: ValidationSchema) => {
      const [isValid,setIsValid] = useState<boolean>(true);
      const [validationState,setValidationState] = useState<ValidationState>(
        createValidationsstate(validationSchema)
      );
    
      /**
       * Executes the value against all provided validation functions and 
       * updates the state.
       * @param key string the name of the property being validated
       * @param value any the value to be tested for validation
       * @return true/false validation
       */
      const runAllValidators = (key: string,value: any,state?: S) => {
        const runValidator = compose(
          (func: Function) => func(value,prop('validation')
        );
        const bools: boolean[] = map(runValidator,validationSchema[key]);
        const isValid: boolean = all(isEqual(true),bools);
        const index: number = indexOf(false,bools);
        const error = index > -1 ? validationSchema[key][index].errorMessage : '';
        const validations: any = {};
        validations[key] = { isValid,error };
        return validations;
      }
    
      /**
       * executes a validation function on a value and updates isValid state
       * @param key string the name of the property being validated
       * @param value any the value to be tested for validation
       * @return true/false validation
       */
      const validate = (key: string,state?: S) => {
        if (key in validationSchema) {
          const validations = runAllValidators(key,value,state);
          setValidationState(mergeDeepRight(validationState,validations));
          setIsValid(validations[key].isValid);
          return validations[key].isValid;
        }
      };
    
      /**
       * updates isValid state if validation succeeds
       * @param key string the name of the property being validated
       * @param value any the value to be tested for validation
       * @return void
       */
      const validateIfTrue = (key: string,value: unkNown,state);
          if (validations[key].isValid) {
            setValidationState(mergeDeepRight(validationState,validations));
          } 
        }
      };
    
      /**
       * Runs all validations against an object with all values and updates/returns
       * isValid state.
       * @param state any an object that contains all values to be validated
       * @return boolean isValid state
       */
      const validateall = (state: S) => {
        const bools = map((key: string) => {
          return validate(key,state[key as keyof S],state);
        },Object.keys(validationSchema));
    
        const result = all(isEqual(true),bools);
        setIsValid(result);
        return result;
      };
    
      /**
       * Get the current error stored for a property on the validation object.
       * @param key the name of the property to retrieve
       * @return string
       */
      const getError = (key: string) => {
        if (key in validationSchema) {
          const val = compose(
            prop('error'),prop(key),);
          return val(validationState);
        }
        return '';
      };
    
      /**
       * Get the current valid state stored for a property on the validation object.
       * @param key the name of the property to retrieve
       * @return boolean
       */
      const getFieldValid = (key: string) => {
        if (key in validationSchema) {
          const val = compose(
            prop('isValid'),);
          return val(validationState);
        }
        return true;
      };
    
      const validationObject = {
        getError,validationState,}
    
      // inititally where I wanted to use the HOC and make it one
      // of the available exports
      const ValidationWrap = InputWrapper(validationObject);
    
      return {
        ...validationObject,ValidationWrap
      };
    };

BasicInput.validation.ts

    import {useValidation} from 'validation.hook';
    
    export interface Dog {
      name: string;
      breed: string;
    }
    
    export const BasicInputValidation = () => {
      return useValidation<Dog>({
        name: [
          {
            errorMessage: 'Cannot be Bob.',validation: (val: string,state: any) => {
              return val.trim().toLowerCase() !== 'bob';
            }
          },{
            errorMessage: 'Cannot be Ross.',state: any) => {
              return val.trim().toLowerCase() !== 'ross';
            }
          },{
            errorMessage: 'Name is required.',state: any) => {
              return val.trim().length > 0;
            }
          },],breed: [
          {
            errorMessage: 'Must be a Leonberger.',state: any) => {
              return val.trim().toLowerCase() === 'leonberger';
            }
          },{
            errorMessage: 'Breed is required.',]
      });
    };

utilities.ts

(createValidationState是运行代码所必需的,其他地方是为了方便复制意大利面

/**
 * Creates a random 7 character string.
 * @return string
 */
export const randomString = () => Math.random().toString(36).substring(7);

/**
 *  Compose function that is a little more friendly to use with typescript.
 *  @param fns any number of comma-separated functions
 *  @return new function
 */
export const compose = (...fns: Function[]) => (x: any) =>
  fns.reduceRight((y: any,f: any) => f(y),x);


// Build Validation State Object
export const createValidationsstate = (schema: ValidationSchema) => {
  const keys = Object.keys(schema);
  return keys.reduce(
    (prev: any,item: string) => {
      prev[item] = {
        isValid: true,error: ''
      };
      return prev;
    },{}
  );
};

以下是一个使用中的示例:

    import React,{ useState } from 'react';
    import {BasicInputValidation} from 'examples/basicInput.validation';
    import {InputWrapper} from 'withValidationComponent';
    import { curry } from 'ramda';
    
    function App() {
      const [state,setState] = useState<{name: string}>({ name: '' });
    
      const onChange = curry((name: string,event: any) => {
        const data = { [name]: event.target.value }
        setState({ ...state,...data });
      })

      const v = BasicInputValidation();
      const HOC = InputWrapper(v);
    
      return (
        <>
          <HOC 
            name="name"
            label="Name"
            onChange={onChange('name')}
            state={state}
          />
        </>
      );
    }
    
    export default App;

解决方法

每个渲染似乎都导致原始InputWrapper组件被卸载。此时,是由于此代码const HOC = InputWrapper({});-每次在父对象上进行渲染时,都会生成一个新的包装器。这在我的实验中很明显:

export const InputWrapper = (v) => {
  const Wrapper = (props) => {
    const { label,onChange,name,state } = props;

    useEffect(() => {
      return () => {
        console.log("unmounting"); // cleanup got invoked everytime I typed in the input
      };
    },[]);

错误的CodeSandBox:https://codesandbox.io/s/react-input-hoc-bugged-e28iz?file=/src/withValidationComponent.js


为解决此问题,在实现方面(即在App组件上),我将包装器实例移到了函数外部

import React,{ useState } from "react";
import {BasicInputValidation} from 'examples/basicInput.validation';
import {InputWrapper} from 'withValidationComponent';
import { curry } from 'ramda';
const HOC = InputWrapper({}); // <-- moved this here

function App() {
  ...

Edit React Input HOC - Fixed

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...