让我们想象一下,我们想要一个"产品的输入" (存储在redux中)价格值。
我很难想出最好的方法来处理输入约束。为简单起见,我们只关注product.price
不能为空的约束。
似乎有两个选项:
实施:输入值绑定到product.price
。在更改时调度changePrice()
操作。
这里的主要问题是,如果我们想要阻止空价进入产品商店,我们实际上阻止用户清除输入字段。这不是理想的,因为它很难改变数字的第一个数字(你必须选择并替换它)!
defaultValue
实施:我们最初使用输入defaultValue
设置价格,这样我们就可以控制何时实际调度changePrice()
操作,并且我们可以在onChange
处理程序。
这很有效,除非product.price
从输入更改事件以外的某处更新(例如,applyDiscount
操作)。由于defaultValue
不会导致重新呈现,product.price
和输入现在不同步!
那我错过了什么?
必须有一个简单的&这个问题的优雅解决方案,但我似乎无法找到它!
答案 0 :(得分:2)
我过去所做的是使用redux-thunk和joi来解决使用受控输入的输入约束/验证。
一般来说,我希望有一个更新操作来处理所有字段更新。因此,例如,如果您有一个表单的两个输入,它将看起来像这样:
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
。
例如,请考虑流动代码段中的这些验证:
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)
所以我认为我找到了一个不错的解决方案。基本上我需要:
创建可以使用本地状态控制输入的单独组件。
将onChange处理程序传递给道具,我可以用它来有条件地发送changePrice
行动
使用componentWillReceiveProps
保持本地值状态与redux商店同步
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} />
}
...