React-admin: [Bug] formMiddleware TypeError: Cannot read property 'type' of undefined for custom async handleSubmit

Created on 30 Jan 2019  路  5Comments  路  Source: marmelab/react-admin

screen shot 2019-01-30 at 13 04 01

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

  • React-admin version: 2.6.1
  • Last version that did not exhibit the issue (if applicable):
  • React version: 16.7.0
  • Browser: Chrome
  • Stack trace (in case of a JS error): As in the screenshot.

Most helpful comment

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!

All 5 comments

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!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

phacks picture phacks  路  3Comments

aserrallerios picture aserrallerios  路  3Comments

yangjiamu picture yangjiamu  路  3Comments

waynebloss picture waynebloss  路  3Comments

alukito picture alukito  路  3Comments