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