简体   繁体   English

访问 props.children 中的每个孩子并触发 function

[英]Visit each child in props.children and trigger a function

I want to be able to visit the children <Textfield> of my form <Form> upon submit.我希望能够在提交时访问我的表单<Form>的子<Textfield> In each child hook object, I also want to trigger a certain function (eg., validate_field).在每个子钩子object中,我还想触发某个function(例如validate_field)。 Not sure if this possible in hooks?不确定这是否可能在钩子中? I do not want to use ref/ useRef and forwardRef is a blurred concept to me yet (if that's of any help).我不想使用 ref/ useRefforwardRef对我来说是一个模糊的概念(如果这有帮助的话)。

My scenario is the form has been submitted while the user did not touch/update any of the textfields so no errors were collected yet.我的情况是表单已提交,而用户没有触摸/更新任何文本字段,因此尚未收集任何错误。 Upon form submit, I want each child to validate itself based on certain constraints.提交表单后,我希望每个孩子都根据某些约束来验证自己。

I tried looking at useImperativeHandle too but looks like this will not work on props.children?我也尝试查看useImperativeHandle但看起来这不适用于 props.children?

Updated working code in: https://stackblitz.com/edit/react-ts-jfbetn更新工作代码: https://stackblitz.com/edit/react-ts-jfbetn


submit_form(evt){
  props.children.map(child=>{
    // hypothetical method i would like to trigger. 
    // this is what i want to achieve
    child.validate_field()  // this will return "is not a function" error
  })
}

<Form onSubmit={(e)=>submit_form(e)}
  <Textfield validations={['email']}>
  <Textfield />
  <Textfield />
</Form>

Form.js表单.js

function submit_form(event){
  event.preventDefault();
  if(props.onSubmit){
    props.onSubmit()
  }
}
export default function Form(props){
  return (
    <form onSubmit={(e)=>submit_form(e)}>
      {props.children}
    </form>
  )
}

So the Textfield would look like this所以文本字段看起来像这样

…
const [value, setValue] = useState(null);
const [errors, setErrors) = useState([]);

function validate_field(){
   let errors = []; // reset the error list 
   props.validations.map(validation => {
     if(validation === 'email'){
       if(!some_email_format_validator(value)){
         errors.push('Invalid email format')
       }
     }
     // other validations (eg., length, allowed characters, etc)
   })
   setErrors(errors)
}

export default function Textfield(props){
  render (
    <input onChange={(evt)=>setValue(evt.target.value)} />
    {
      errors.length > 0 
      ? errors.map(error => {
        return (
          <span style={{color:'red'}}>{error}</span>
        )
      })
      : null
    }
  )
}

I would recommend moving your validation logic up to the Form component and making your inputs controlled.我建议将您的验证逻辑移至 Form 组件并控制您的输入。 This way you can manage the form state in the parent of the input fields and passing in their values and onChange function by mapping over your children with React.cloneElement.这样,您可以通过使用 React.cloneElement 映射到您的子项来管理输入字段的父项中的 state 表单并传入它们的值和 onChange function。

I don't believe what you're trying to do will work because you are trying to map over the children prop which is not the same as mapping over say an array of instantiated child elements.我不相信您尝试做的事情会起作用,因为您正在尝试 map 超过 children 道具,这与映射到一个实例化的子元素数组不同。 That is to say they don't have state, so calling any method on them wouldn't be able to give you what you wanted.也就是说他们没有 state,所以在他们身上调用任何方法都无法给你想要的东西。

You could use a complicated system of refs to keep the state in your child input elements, but I really don't recommend doing that as it would get hairy very fast and you can just solve the issue by moving state up to the parent.可以使用复杂的参考系统将 state 保留在您的子输入元素中,但我真的不建议这样做,因为它会很快变得多毛,您可以通过将 state 移动到父元素来解决问题。

simplified code with parent state:带有父 state 的简化代码:

const Form = ({ children }) => {
  const [formState, setFormState] = useState(children.reduce((prev, curr) => ({ ...prev, [curr.inputId]: '' }), {}));

  const validate = (inputValue, validator) => {}
  const onSubmit = () => {
    Object.entries(formState).forEach(([inputId, inputValue]) => {
      validate(
        inputValue,
        children.filter(c => c.inputId === inputId)[0].validator
      )
    })
  }

  const setFieldValue = (value, inputId) => {
    setFormState({ ...formState, [inputId]: value });
  };
  const childrenWithValues = children.map((child) =>
    React.cloneElement(child, {
      value: formState[child.inputId],
      onChange: (e) => {
        setFieldValue(e.target.value, child.inputId);
      },
    }),
  );

  return (
    <form onSubmit={onSubmit}>
      {...childrenWithValues}
    </form>
  )
};

const App = () => 
  <Form>
    <MyInput validator="email" inputId="foo"/>
    <MyInput validator="email" inputId="foo"/>
    <MyInput validator="password" inputId="foo"/>
  </Form>

I still don't love passing in the validator as a prop to the child, as pulling that out of filtered children is kinda jank.我仍然不喜欢将验证器作为道具传递给孩子,因为将其从过滤后的孩子中拉出来有点笨拙。 Might want to consider some sort of state management or pre-determined input list.可能需要考虑某种 state 管理或预先确定的输入列表。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM