我在我的应用程序中使用redux-observable并尝试为我的情况添加redux-form(异步验证,跟踪所有操作),我需要创建一个在每次数据更改时都进行异步调用的表单。 我不想手动提交表单并提出中央提交请求(正如官方的例子那样)。
考虑到redux-form正在为每个字段更改调度一个动作@@redux-form/CHANGE
,很容易利用redux-observable并“监听”此操作并应用一些限制,取消,重试等基于这些行动。每个异步调用(一旦将某些内容保存到数据库中)都会返回特定字段的ID。
基于以下代码示例,取自redux-form-redux-observable
const registerEpic = (action$, { getState, dispatch }) =>
action$.ofType('REQUEST_SUBMIT')
.do(dispatch(startSubmit('contact')))
.mergeMap(action =>
fromPromise(submitToServer(action.data))
.map(response => {
console.log(response);
dispatch(stopSubmit('contact', response.errors || {}));
if (response.errors) {
return {
type: 'REQUEST_FAILED',
errors: response.errors,
}
} else {
return {
type: 'REQUEST_SUCCESSFUL'
}
}
})
);
我尝试处理从redux-form调度的操作。当调度@@redux-form/CHANGE
时,它会进行服务器调用,当此调用解析时,我从服务器获取字段ID。我不知何故需要将它添加到表单的存储中,并在可能需要对服务器执行此特定字段的DELETE请求时将其用作“将来使用”的引用。
我认为解决问题的方法是在从服务器解析数据时调度redux-form change
操作,但这会再次触发@@redux-form/CHANGE
并且表单(显然)进入一旦我继续听@@redux-form/CHANGE
动作,我就会无限循环。
const updateEpic = (action$, { getState, dispatch }) =>
action$.ofType('@@redux-form/CHANGE')
.debounceTime(1000)
.do(x => console.log(x))
.do(x => console.log(getState().form.contact.values[x.meta.field]))
.mergeMap(action =>
fromPromise(submitToServer(xxx))
.map(response => {
dispatch(change('contact', 'firstName', 'test'));
})
);
所以我在这里遇到了一些棘手的案例:
如何将返回的ID从服务器添加到商店(供将来使用)
如何巧妙地避免这种无限,通过引入另一种方式来处理我的案例
最后我想知道redux-observable和redux-form的组合是否符合我的需要。
谢谢
我终于设法通过使用reducer.plugin()调度新操作来解决此问题,并将服务器返回的数据应用于每个字段的meta
数据。
下面是一个简单的例子
const updateEpic = (action$, { getState, dispatch }) =>
action$.ofType('@@redux-form/CHANGE')
.debounceTime(1000)
.do(x => console.log(x))
.do(x => console.log(getState().form.contact.values[x.meta.field]))
.mergeMap(action =>
fromPromise(submitToServer(xxx))
.map(response => {
return {
action: 'CUSTOM_ACTION',
payload: {
id: response.id,
meta: action.meta // <-- this is the initial action that helps you to know which is the field you need to update
}
}
})
);
为减速器方
myReducer(state = {}, action) {
switch (action.type) {
case 'CUSTOM_ACTION':
return merge({}, state, {
[action.payload.meta.form]: {
fields: {
[action.payload.meta.field]: {
id: action.payload.id
}
}
}
})
break;
default:
return state
}
}
最后,您可以从React Component
中的id
获取meta
const renderField = ({ type, label, input, meta: { touched, error, warning, id = undefined } }) => (
<div className="input-row">
<pre>{id}</pre>
<label>{label}</label>
<input {...input} type={type}/>
{touched && error &&
<span className="error">{error}</span>}
{touched && warning &&
<span className="warning">{warning}</span>}
</div>
);
如果其他人有更好的解决方案,我想告诉我们。把这个问题视为从我这边关闭。
答案 0 :(得分:0)
@stelioschar,我研究了它,找到了你的方法,我相信这是非常有效的,我在这里留下我的代码,如果其他人对待它,分享。
我正在验证,当用户输入时,当它达到6个字母时,我向服务器发送请求:
export const validateUserNameEpic = function (action$, store) {
return action$.ofType('@@redux-form/CHANGE')
.filter(action => action.meta.field === "userName")
.filter(action => (!utility.validation.lengthMin6(action.payload) && !utility.validation.userName(action.payload)))
.debounceTime(500)
.mergeMap(action =>
Observable.ajax({
url: `${apiUrl}/auth/validations/v1`,
method: 'POST',
body: JSON.stringify({
type: 'user-name',
value: action.payload
}),
headers: {
'Content-Type': 'text/plain'
},
crossDomain: true
})
.map(data => {
const previousErrors = store.getState().form.signUp.asyncErrors;
if (data.response === 'true' || data.response === true){
return {
type: "@@redux-form/STOP_ASYNC_VALIDATION",
meta:{
field: "userName",
form: "signUp",
touch: true
},
error: true,
payload: Object.assign({}, previousErrors, { userName : "b591d5f.0450d1b.76ae7b1.d" })
}
} else {
return {
type: "@@redux-form/STOP_ASYNC_VALIDATION",
meta:{
field: "userName",
form: "signUp",
touch: true
},
error: false,
payload: previousErrors
}
}
})
.startWith({
type: "@@redux-form/START_ASYNC_VALIDATION",
meta:{
field: "userName",
form: "signUp"
}
})
.startWith({
type: "@@redux-form/BLUR",
meta:{
field: "userName",
form: "signUp",
touch: true
}
})
.catch(error => Observable.of({
type: "@@redux-form/STOP_ASYNC_VALIDATION",
meta:{
field: "userName",
form: "signUp"
}
}))
)
}