简体   繁体   中英

How to handle async update onUpdate of each field and keep ids using redux-form and redux-observable

I'm using redux-observable in my app and try to add redux-form(async validation, tracing all the actions) for my case where I need to make a form that makes async calls in every change of data. I don't want to manually submit the form and make a central submit request(as the official examples do).

Taking into account that redux-form is dispatching an action @@redux-form/CHANGE on each field change, is very easy to take advantage of redux-observable and 'listen' to this action and apply some throttling, cancellation, retry etc based on these actions. Each Async call(once it saves something to the db) returns an ID for the specific field.

Based on the code example below, taken from 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'
          }
        }
      })
    );

I tried handle the action that are being dispatched from redux-form. When the @@redux-form/CHANGE is being dispatched, it makes a server call and when this call resolves I get the field ID from the server. I somehow need to add it to the form's store and use it as a reference for a 'future use' once a DELETE request for this specific field may be needed to be done to the server.

The way I thought to solve the problem is to dispatch the redux-form change action when the data are resolved from the server but this triggers the @@redux-form/CHANGE again and the form(obviously) enters in an infinite loop once I keep listen for the @@redux-form/CHANGE action.

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'));
      })
    );

So I have some struggling cases here:

  • How I could add the returned Id from the server to the store(for future use)

  • How I could smartly avoid this infinite, by introducing another way to handle my case

  • Finally I'm wondering if the combination of the redux-observable along with redux-form fit my needs.

Thank you

Update

Solution

I finally managed to resolve this issue by dispatching a new action use the reducer.plugin() and apply the returned data from the server to the meta data of each field.

A simple example is below

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
                        }
                    }
                })
        );

for the reducer side

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
    }
}

Finally you can take the id from the meta in React Component

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>
);

If anyone else has a better solution I would like to let us know. Consider this issue as closed from my side.

@stelioschar, I researched it, found you approach, I believe it is very efficient, I am leaving my code here, if others treat it differently, share.

I am validating, as the user is typing, when it reaches 6 letters, I send a request to the server:

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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