foxhound87 / mobx-react-form

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

onSuccess callback & GraphQL #245

Closed mxmtsk closed 7 years ago

mxmtsk commented 7 years ago

Is it possible to call a function on the component where the form is imported and displayed when I submit the form and validation passed?

I have some higher order components on my components (for example I'm using react-apollo for handling GraphQL requests) that bind certain methods to this.props that I don't have access to in the onSuccess method of the form, because that is not a real react component.

I'd rather have the onSuccess method live in the Component where it get's used

LoginForm.js

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

const plugins = { dvr: validatorjs };

const fields = [{
  name: 'email',
  label: 'Email',
  rules: 'required|email',
}, {
  name: 'password',
  label: 'Password',
  type: 'password',
  rules: 'required',
}];

class LoginForm extends MobxReactForm {
  onSuccess = (form) => {
    // i have no access to my login mutation here 
    console.log('Form Values!', form.values());
  }

  onError = (form) => {
    // get all form errors
    console.log('All form errors', form.errors());
    // invalidate the form with a custom error message
    form.invalidate('This is a generic error message!');
  }
}

export default new LoginForm({ fields }, { plugins });

Login.js


import React from 'react';
import withStyles from 'isomorphic-style-loader/lib/withStyles';
import { inject, observer } from 'mobx-react';
import { gql, graphql } from 'react-apollo';
import cx from 'classnames';
import s from './Login.css';
import form from './LoginForm';
[... more imports]

class Login extends React.Component {
  static propTypes = {
    login: React.PropTypes.func.isRequired,
  }

  // I'd like to call this method on submit
  tryLogin = (form) => {
    this.props.login({
      email: ....,
      password: ....,
    })
    .then(() => {
      ... success
    })
    .catch(() => {
      ... error
    });
  }

  render() {
    return (
      <Page>
        <PageHeader />
        <PageInner>
          <GridColumn color={2} third>
            <div className={s.content}>
              <h1 className={cx('h2 p-b-2', s.headlineSpace)}>
                login
              </h1>
              <form onSubmit={form.onSubmit}>
                {form.error ? <Alert>{form.error}</Alert> : null}

                <Input field={form.$('email')} />
                <Input field={form.$('password')} />

                <div className={s.submit}>
                  <Button type="submit" onClick={form.onSubmit}>Login</Button>
                </div>
              </form>
            </div>
          </GridColumn>
        </PageInner>
      </Page>
    );
  }
}

const login = gql`
  mutation login($email: String!, $password: String!) {
    login(email: $email, password: $password) {
      id
      email
      token
    }
  }
`;

export default inject('store')(graphql(login, { name: 'login' })(withStyles(s)(observer(Login))));
mxmtsk commented 7 years ago

I came up with this workaround, which does work but kinda breaks server side rendering with the following warning. So I'm still happy and open for suggestions or if you would consider adding support for this.

Warning: React attempted to reuse markup in a container but the checksum was invalid. This generally means that you are using server rendering and the markup generated on the server was not what the client was expecting. React injected new markup to compensate which works but you have lost many of the benefits of server rendering. Instead, figure out why the markup being generated is different on the client or server: (client) ="text" id="email--32" name="email" valu (server) ="text" id="email--34" name="email" valu

Changed my LoginForm.js to

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

const plugins = { dvr: validatorjs };

const fields = [{
  name: 'email',
  label: 'Email',
  rules: 'required|email',
}, {
  name: 'password',
  label: 'Password',
  type: 'password',
  rules: 'required',
}];

class LoginForm extends MobxReactForm {
  constructor(successCallback) {
    super({ fields }, { plugins });

    this.callback = successCallback;
  }

  onSuccess = (form) => {
    this.callback(form);
  }

  onError = (form) => {
    // get all form errors
  }
}

export default LoginForm;

And in my Login.js

class Login extends React.Component {
  static propTypes = {
    loginUser: React.PropTypes.func.isRequired,
  }

  constructor(props) {
    super(props);
    // initialize the form
    this.form = new LoginForm(this.login);
  }

  login = (form) => {
    // works
    console.log(form.values());
  } 
[...]
foxhound87 commented 7 years ago

Where tryLogin is used?

Have you tried to Override the Validation Handlers into the component?

foxhound87 commented 7 years ago

I think the problem about the SSR is because you have to initialize the Form instance outside the component.

mxmtsk commented 7 years ago

thank you @foxhound87, Overriding validation handlers is exactly what I was looking for. I missed that in the documentation :)

kitze commented 7 years ago

Hey @mxmtsk, just note that you don't have to use onSuccess or the other handlers in the mobx react form class.

Usually, I define my own submit handlers somewhere in one of my mobx store classes, and I basically use mobx-react-form for just defining the forms and exporting them.

foxhound87 commented 7 years ago

@kitze now the built-in event handlers are able to also handle promises, if you use them you can track the new submitting observable prop.