foxhound87 / mobx-react-form

Reactive MobX Form State Management
https://foxhound87.github.io/mobx-react-form
MIT License
1.1k stars 129 forks source link

How can I make an API call onSubmit and handle the errors? #409

Closed betancourtl closed 6 years ago

betancourtl commented 6 years ago

I'm looking at the docs it's not clear to me where to make an API call when submitting the form. I also need to find out how to show server errors for each field.

I tried adding an onSubmit function to the class and it gets called but the hooks do not work.

Store

import MobxReactForm from 'mobx-react-form';
import validatorjs from 'validatorjs';

const plugins = { dvr: validatorjs };

const fields = [
  {
    name: 'email',
    label: 'Email',
    placeholder: 'Insert Email',
    rules: 'required|email|string|between:5,25',
    value: 's.jobs@apple.com'
  },
  {
    name: 'password',
    label: 'Password',
    placeholder: 'Insert Password',
    rules: 'required|string|between:5,25',
  }
];

const hooks = {
  onSuccess(form) {
  //  I want to handle success here
    console.log('Form Values!', form.values());
  },
  onError(form) {
    //  I want to handle errors here
    console.log('All form errors', form.errors());
  }
};

class LoginForm extends MobxReactForm {
  static storeName = 'loginForm';

  constructor() {
    super({ fields }, { plugins, hooks });
  }
}

export default LoginForm;

const storeName = LoginForm.storeName;

export {
  storeName,
}

Component

import React, { Component } from 'react';
import Header from '../../components/Header';
import { Grid, Row } from 'react-bootstrap';
import { observer, inject } from 'mobx-react';
import PropTypes from 'prop-types';

@inject('loginForm')
@observer
class Test extends Component {
  render() {
    const form = this.props.loginForm;
    console.log(form);
    const email = form.$('email');
    const password = form.$('password');
    return (
      <form onSubmit={form.onSubmit}>

        <div>
          <input
            type="text"
            {...email.bind()}
          />
          <p>{email.error}</p>
        </div>

        <div>
          <input
            type="text"
            {...password.bind()}
          />
          <p>{password.error}</p>
        </div>

        <button>Submit</button>

      </form>
    );
  }
}

Test.propTypes = {};

Test.defaultProps = {};

export default Test;
betancourtl commented 6 years ago

I ended up submitting the form inside the onSuccess hook, and it is showing up the server errors correctly now. Anyone knows is this is the correct way to handle server errors after an API call?

import MobxReactForm from 'mobx-react-form';
import validatorjs from 'validatorjs';
import { authenticate } from "../api/auth";

const plugins = { dvr: validatorjs };

const fields = [
  {
    name: 'email',
    label: 'Email',
    placeholder: 'Insert Email',
    rules: 'required|email|string|between:5,25',
    value: 'webdeveloperpr@gmail.com'
  },
  {
    name: 'password',
    label: 'Password',
    placeholder: 'Insert Password',
    rules: 'required|string|between:5,25',
    value: '123qweQWE',
    type: 'text'
  }
];

const hooks = {
  onSuccess(form) {
    //  I want to handle success here
    authenticate({
      email: form.$('email').value,
      password: form.$('password').value,
    })
      .then(({ user, token }) => {
        this.rootStore.session.startSession(user, token);
      })
      .catch(err => {

        if (err.email) {
          form.validate('email')
            .then(({ isValid }) => {
              form.$('email').invalidate(err.email);
            });
        }

        if (err.password) {
          form.validate('password')
            .then(({ isValid }) => {
              form.$('password').invalidate(err.password);
            });
        }

      });
  },
  onError(form) {
    // Not really sure why I would handle anything in here ¯\_(ツ)_/¯
    console.log('All form errors', form.errors());
  }
};

class LoginForm extends MobxReactForm {
  static storeName = 'loginForm';

  constructor() {
    super({ fields }, { plugins, hooks });
  }
}

export default LoginForm;

const storeName = LoginForm.storeName;

export {
  storeName,
}
betancourtl commented 6 years ago

Now im running into issues. The async validation works fine and errors show up but then the sync validation kicks in and the async errors dissappear...... How do I preserve the async errors until the next time the user submits?

betancourtl commented 6 years ago

Ok I ended up setting the errors using a timeout to bypass the issue. This feels so hacky.

import MobxReactForm from 'mobx-react-form';
import validatorjs from 'validatorjs';
import { authenticate } from "../api/auth";

const plugins = { dvr: validatorjs };

const fields = [
  {
    name: 'email',
    label: 'Email',
    placeholder: 'Insert Email',
    rules: 'required|string|between:5,40|email',
    value: 'webdeveloperpr@gmail.com'
  },
  {
    name: 'password',
    label: 'Password',
    placeholder: 'Insert Password',
    rules: 'required|string|between:5,25',
    value: '123qweQWE',
    type: 'text'
  }
];

const hooks = {
  onSuccess(form) {
    //  I want to handle success here
    authenticate({
      email: form.$('email').value,
      password: form.$('password').value,
    })
      .then(({ user, token }) => {
        this.rootStore.session.startSession(user, token);
      })
      .catch(err => {

      // Prevents the text input's onBlur event from removing the async errors. The unblur event triggers a validation check.
        setTimeout(() => {
          if (err.email) {
            form.validate('email')
              .then(({ isValid }) => {
                console.log('isValid', isValid);
                form.$('email').invalidate(err.email);
              });
          }

          if (err.password) {
            form.validate('password')
              .then(({ isValid }) => {
                form.$('password').invalidate(err.password);
              });
          }

        }, 250);
      });
  },
  onError(form) {
    //  I want to handle errors here
    console.log('All form errors', form.errors());
  }
};

class LoginForm extends MobxReactForm {
  static storeName = 'loginForm';

  constructor() {
    super({ fields }, { plugins, hooks });
  }
}

export default LoginForm;

const storeName = LoginForm.storeName;

export {
  storeName,
}
michalpleszczynski commented 6 years ago

I had the same problem, my code is nearly identical to @webdeveloperpr, but I solved it storing the error message in a store and clearing it up on componentWillMount:

class MobxLoginForm extends MobxReactForm {
  ...
  hooks() {
    return {
      onSuccess(form) {
        const { email, password } = form.values();
        this.session.logIn(
          email, password
        ).then((response) => {
          if (!response.success && response.payload.length > 0) {
            form.invalidate();
            this.session.setLoginError(response.payload[0]); // set the error message in the store
            if (this.handleError != null) {
              this.handleError();
            }
          } else if (this.handleSuccess != null) {
            this.handleSuccess();
          }
        });
      },
      // other hooks here

and in my component that is rendering the form:

@inject('session') @observer
class LoginForm extends React.Component {
  componentWillMount() {
      this.props.session.setLoginError(null);
  }

  render() {
    ...
    {session.loginError ? <p className="error">{session.loginError}</p>}

This works quite well, but it would be great to have a better way.

foxhound87 commented 6 years ago

The onSuccess hook should return a promise to work.

elliotykim commented 5 years ago

seems like there's a bug here. @michalpleszczynski, could you take a look at this?

Steps to reproduce:

  1. open https://codesandbox.io/s/kkvqv87p3v
  2. enter password and password confirmation "aaaaa"
  3. click on "email" to set focus
  4. click "Submit" button

Expected outcome Email field is invalidated with "whatever reason"

Actual outcome Email field briefly flashes invalidated state and then becomes validated

elliotykim commented 5 years ago

On a side note, focus() doesn't appear to work consistently for fields. I tried forcing focus on the field before/after invalidate() so the async validation wouldn't clear my error but had mixed results.

In the end, I used these options with success:

const options = {
  validateOnBlur: false,
  validateOnChange: true
}

@webdeveloperpr