如何处理每个字段的异步更新onUpdate并使用redux-form和redux-observable保留id

时间:2017-07-19 15:23:30

标签: javascript reactjs redux redux-form redux-observable

我在我的应用程序中使用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>
);

如果其他人有更好的解决方案,我想告诉我们。把这个问题视为从我这边关闭。

1 个答案:

答案 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"
                }
            }))
        )
}