React / Redux控制输入并进行验证

时间:2017-12-05 19:36:57

标签: reactjs redux

让我们想象一下,我们想要一个"产品的输入" (存储在redux中)价格值。

我很难想出最好的方法来处理输入约束。为简单起见,我们只关注product.price不能为空的约束。

似乎有两个选项:

1:受控

实施:输入值绑定到product.price。在更改时调度changePrice()操作。

这里的主要问题是,如果我们想要阻止空价进入产品商店,我们实际上阻止用户清除输入字段。这不是理想的,因为它很难改变数字的第一个数字(你必须选择并替换它)!

2:使用defaultValue

实施:我们最初使用输入defaultValue设置价格,这样我们就可以控制何时实际调度changePrice()操作,并且我们可以在onChange处理程序。

这很有效,除非product.price从输入更改事件以外的某处更新(例如,applyDiscount操作)。由于defaultValue不会导致重新呈现,product.price和输入现在不同步!

那我错过了什么?

必须有一个简单的&这个问题的优雅解决方案,但我似乎无法找到它!

3 个答案:

答案 0 :(得分:2)

我过去所做的是使用redux-thunkjoi来解决使用受控输入的输入约束/验证。

一般来说,我希望有一个更新操作来处理所有字段更新。因此,例如,如果您有一个表单的两个输入,它将看起来像这样:

render() {
  const { product, updateProduct } = this.props;
  return (
    <div>
      <input 
        value={product.name}
        onChange={() => updateProduct({...product, name: e.target.value})}
      />
      <input 
        value={product.price}
        onChange={() => updateProduct({...product, price: e.target.value})}
      />
    </div>
  )
}

这里有一个功能/动作可以简化我的表格。然后updateProject操作将成为处理副作用的thunk动作。这是我们的Joi Schema(基于您的一个要求)和上面提到的updateProduct Action。作为旁注,我也倾向于让用户犯错误。因此,如果他们没有按价格输入任何内容,我只会将提交按钮设置为非活动状态,但仍会在redux存储中存储空/空字符串。

const projectSchema = Joi.object().keys({
  name: Joi.number().string(),
  price: Joi.integer().required(), // price is a required integer. so null, "", and undefined would throw an error.
});

const updateProduct = (product) => {
  return (dispatch, getState) {
    Joi.validate(product, productSchema, {}, (err, product) => {
      if (err) {
        // flip/dispatch some view state related flag and pass error message to view and disable form submission;
      }

    });
    dispatch(update(product)); // go ahead and let the user make the mistake, but disable submission
  }
}

我停止使用不受控制的输入,因为我喜欢捕获应用程序的整个状态。我的项目中只有很少的本地组件状态。请记住,这是sudo代码,如果直接复制粘贴,可能无法正常工作。希望它有所帮助。

答案 1 :(得分:1)

在这种情况下,我要做的是验证输入onBlur而不是onChange

例如,请考虑流动代码段中的这些验证:

  1. 输入不能为空。
  2. 输入不应包含“foo”。
  3. class App extends React.Component {
    
      constructor(props) {
        super(props);
        this.state = {
          myVal: '',
          error: ''
        }
      }
    
      setError = error => {
        this.setState({ error });
      }
    
      onChange = ({ target: { value } }) => {
        this.setState({ myVal: value })
      }
    
      validateInput = ({ target: { value } }) => {
        let nextError = '';
        if (!value.trim() || value.length < 1) {
          nextError = ("Input cannot be empty!")
        } else if (~value.indexOf("foo")) {
          nextError = ('foo is not alowed!');
        }
    
        this.setError(nextError);
      }
    
      render() {
        const { myVal, error } = this.state;
        return (
          <div>
            <input value={myVal} onChange={this.onChange} onBlur={this.validateInput} />
            {error && <div>{error}</div>}
          </div>
        );
      }
    }
    
    ReactDOM.render(<App />, document.getElementById('root'));
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
    <div id="root"></div>

    修改

    作为您评论的后续内容。

    为了使这个解决方案更通用,我会将组件作为一个谓词函数传递给prop,只有当函数返回一个有效的结果时,我才会调用从父级传递的onChange或你通过的任何方法更新商店。

    通过这种方式,您可以在应用程序(甚至其他项目)的其他组件和位置重用此模式。

答案 2 :(得分:1)

所以我认为我找到了一个不错的解决方案。基本上我需要:

  1. 创建可以使用本地状态控制输入的单独组件。

  2. 将onChange处理程序传递给道具,我可以用它来有条件地发送changePrice行动

  3. 使用componentWillReceiveProps保持本地值状态与redux商店同步

  4. 代码(简化和打字稿):

    interface INumberInputProps {
      value: number;
      onChange: (val: number) => void;
    }
    
    interface INumberInputState {
      value: number;
    }
    
    export class NumberInput extends React.Component<INumberInputProps, INumberInputState> {
    
      constructor(props) {
        super(props);
        this.state = {value: props.value};
      }
    
      public handleChange = (value: number) => {
        this.setState({value});
        this.props.onChange(value);
      }
    
      //keeps local state in sync with redux store
      public componentWillReceiveProps(props: INumberInputProps){
        if (props.value !== this.state.value) {
          this.setState({value: props.value});
        }
      }
    
      public render() {
        return <input value={this.state.value} onChange={this.handleChange} />
      }
    }
    

    在我的产品组件中:

      ...
      //conditionally dispatch action if meets valadations
      public handlePriceChange = (price: number) => {
        if (price < this.props.product.standardPrice &&
          price > this.props.product.preferredPrice &&
          !isNaN(price) &&
          lineItem.price !== price){
            this.props.dispatch(updatePrice(this.props.product, price));
        }
      }
    
      public render() {
         return <NumberInput value={this.props.product.price} onChange={this.handlePriceChange} />
      }
    

    ...