
What you were expecting:
When using a custom async handleSubmit for SaveButton, there shouldn't be any error after saving the form.
What happened instead:
After saving the form successfully (endpoint is called), it's showing a TypeError: Cannot read property 'type' of undefined in formMiddleware.js, as shown in the screenshot.
Related code:
I made this custom save button in order to allow a custom handler to change the form values before they are submitted.
import React, { Component } from 'react';
import { SaveButton, crudUpdate, crudCreate } from 'react-admin';
import { connect } from 'react-redux';
const saveHandler = (values, basePath, redirectTo, isEdit, handler, resource, crudCreate, crudUpdate) => {
const modifiedValues = { ...values };
handler(modifiedValues, isEdit).then(() => {
if (isEdit) {
return crudUpdate(resource, modifiedValues.id, modifiedValues, values, basePath, redirectTo);
}
return crudCreate(resource, modifiedValues, basePath, redirectTo);
});
}
const SaveButtonView = props => {
const handleClick = () => {
const {
basePath,
handleSubmit,
redirect,
resource,
saveHandler,
handler,
isEdit,
crudCreate,
crudUpdate
} = props;
return handleSubmit(values => {
return saveHandler(values, basePath, redirect, isEdit, handler, resource, crudCreate, crudUpdate);
});
};
const {
handleSubmitWithRedirect,
handleSubmit,
saveHandler,
isEdit,
handler,
crudCreate,
crudUpdate,
...rest
} = props;
return (
<SaveButton
handleSubmitWithRedirect={ handleClick }
{ ...rest}
/>
);
}
export default connect(null, { saveHandler, crudCreate, crudUpdate})(SaveButtonView);
The usage:
const saveHandler = async (values, isEdit) => {
const latlon = await geoCoding(values.location);
values.location.geoPoint = latlon;
}
const CreateFormToolbar = ({ isEdit, ...props }) => {
return (
<Toolbar {...props} >
<SaveFormButton
handler={ saveHandler }
resource='Product'
isEdit={ isEdit }
label='Save'
redirect='list'
submitOnEnter={false}
/>
</Toolbar>
);
};
Environment
I think it was recently fixed in #2825, yet to be released
@fzaninotto It looks like it was my mistake in the previous piece of code. I think this one looks better:
import React, { Component } from 'react';
import { SaveButton, crudUpdate, crudCreate } from 'react-admin';
import { connect } from 'react-redux';
const saveHandler = (values, basePath, redirectTo, isEdit, handler, resource) => {
const modifiedValues = { ...values };
return dispatch => {
return handler(modifiedValues, isEdit).then(() => {
if (isEdit) {
dispatch(crudUpdate(resource, modifiedValues.id, modifiedValues, values, basePath, redirectTo));
}
dispatch(crudCreate(resource, modifiedValues, basePath, redirectTo));
})
};
}
const SaveButtonView = props => {
const handleClick = () => {
const {
basePath,
handleSubmit,
redirect,
resource,
handler,
isEdit,
dispatch
} = props;
return handleSubmit(values => {
const action = saveHandler(values, basePath, redirect, isEdit, handler, resource)();
dispatch(action);
});
};
const {
handleSubmitWithRedirect,
handleSubmit,
isEdit,
handler,
dispatch,
...rest
} = props;
return (
<SaveButton
handleSubmitWithRedirect={ handleClick }
{ ...rest}
/>
);
}
export default connect(null)(SaveButtonView);
However, now I got 2 new errors:
handleSubmit.js:64 Uncaught Error: Actions must be plain objects. Use custom middleware for async actions.
at Object.performAction (<anonymous>:1:34471)
at y (<anonymous>:1:36743)
at e (<anonymous>:1:40562)
at middleware.js:13
at formMiddleware.js:22
at middleware.js:72
and:
Unhandled Rejection (TypeError): dispatch is not a function
Could it be that you're not using redux-thunk in react-admin?
we're not using redux-thunk.
@fzaninotto Thanks for replying! I think I misread somewhere about react-admin and assumed that it's also using redux-thunk so it's my own mistake. I agree that redux-saga is more powerful but with redux-thunk, it'll make something like above more simple. I checked that react-admin supports custom sagas but every custom sagas will need to registered globally with <Admin/>, which in my opinion is a bit verbose. Do you think we can implement the above code with redux-saga in a simpler way?
I managed to get it work with redux-saga with this:
import { call, put, all, takeLatest } from 'redux-saga/effects';
import { crudUpdate, crudCreate } from 'react-admin';
function* saveHandler({ payload: { values, basePath, redirectTo, isEdit, handler, resource } }) {
const modifiedValues = { ...values };
const result = yield call(handler, modifiedValues, isEdit);
if (isEdit) {
yield put(crudUpdate(resource, modifiedValues.id, modifiedValues, values, basePath, redirectTo));
} else {
yield put(crudCreate(resource, modifiedValues, basePath, redirectTo));
}
}
export default function* saveHandlerSaga() {
yield all([
takeLatest('FORM_SAVE', saveHandler)
]);
}
Thanks for your support @fzaninotto! react-admin is awesome!
Most helpful comment
I managed to get it work with
redux-sagawith this:Thanks for your support @fzaninotto! react-admin is awesome!