Skip to content

Instantly share code, notes, and snippets.

@romanlex
Last active February 28, 2024 01:57
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save romanlex/bf61fb0c3db2ef1a6c68cb07a0904da2 to your computer and use it in GitHub Desktop.
Save romanlex/bf61fb0c3db2ef1a6c68cb07a0904da2 to your computer and use it in GitHub Desktop.
react-final-form + validation.js + Fragment + Portal + outside submitting [example: https://codesandbox.io/s/j3qry8r38y]
import React, { Component } from 'react';
import Type from 'prop-types';
import { DividerChildContent } from 'ui/components/DividerBlock';
import CallbackForm from './CallbackForm';
class Callback extends Component {
render() {
const containerActionId = `CallbackFormActions`;
return (
<DividerChildContent noMargin className="callback-form__wrapper">
<div className="mt-auto" />
<CallbackForm
buttonPortalId={containerActionId}
subscription={{ submitting: true, pristine: true }}
/>
<div className="mt-auto" />
<footer
className="divider-block__footer-actions"
id={containerActionId}
/>
</DividerChildContent>
);
}
}
Callback.propTypes = {};
Callback.defaultProps = {};
export default Callback;
import React, { PureComponent, Fragment } from 'react';
import ReactDOM from 'react-dom';
import Type from 'prop-types';
import validate from 'validate.js';
import InputMask from 'react-input-mask';
import cx from 'classnames';
import { Form, Field, FormSpy } from 'react-final-form';
import { RegexPhone } from 'ui/lib/phones';
import { FormGroup } from 'reactstrap';
import { autobind } from 'core-decorators';
const constraints = {
name: {
presence: {
message: 'Пожалуйста, укажите корректное имя',
},
format: {
pattern: '[a-zа-яёЁ0-9]+',
flags: 'i',
message: 'Имя может содержать только буквы и цифры, знак пробела',
},
},
phone: {
presence: {
message: 'Укажите корректный номер телефона',
},
format: {
pattern: RegexPhone['ru-RU'],
flags: 'i',
message: 'Укажите корректный номер телефона',
},
},
};
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
class FormButtons extends PureComponent {
constructor(props) {
super(props);
this.el = document.createDocumentFragment();
}
componentDidMount() {
const { portalSelector } = this.props;
document.getElementById(portalSelector).appendChild(this.el);
}
componentWillUnmount() {
const { portalSelector } = this.props;
document.getElementById(portalSelector).removeChild(this.el);
}
render() {
const { submit, reset, submitting, pristine } = this.props;
return ReactDOM.createPortal(
<Fragment>
<button
className="btn btn-primary btn-block"
type="submit"
onClick={submit}
disabled={submitting}
>
Заказать
</button>
<button
className="btn btn-primary btn-block"
type="button"
onClick={reset}
disabled={submitting || pristine}
>
Сбросить
</button>
</Fragment>,
this.el
);
}
};
class CallbackForm extends PureComponent {
@autobind
async submitHandler(values) {
await sleep(300);
window.alert(JSON.stringify(values, 0, 2));
}
render() {
const { subscription, buttonPortalId } = this.props;
return (
<Form
onSubmit={this.submitHandler}
subscription={subscription}
validate={values => {
console.log(values);
const errors = {};
const data = { ...values };
if (data.phone) data.phone = data.phone.replace(/[()_\-\s]/g, '');
const validatedMessages =
validate(data, constraints, { fullMessages: false }) || {};
Object.keys(validatedMessages).map(key => {
errors[key] = validatedMessages[key].join('<br />') || '';
});
return errors;
}}
render={({ handleSubmit, reset, submitting, pristine, values }) => (
<form
id={`callback-form`}
onSubmit={handleSubmit}
className="form form--default callback-form"
>
<Field name="name">
{({ input, meta }) => {
const controlClasses = cx({
'form-control': true,
'is-invalid': meta.touched && meta.invalid && meta.error,,
});
return (
<FormGroup>
<label
htmlFor="name"
className="col-form-label col-form-label--required"
>
Имя
</label>
<input
{...input}
type={`text`}
className={controlClasses}
autoComplete={`off`}
/>
{meta.touched &&
meta.error && (
<div className="invalid-feedback">{meta.error}</div>
)}
<small
id="nameHelpBlock"
className="form-text form-text--help"
>
Мы же не можем обращаться к вам без имени, правда?!
</small>
</FormGroup>
);
}}
</Field>
<Field name="phone">
{({ input, meta }) => {
const controlClasses = cx({
'form-control': true,
'is-invalid': meta.touched && meta.invalid && meta.error,,
});
return (
<FormGroup>
<label
htmlFor="name"
className="col-form-label col-form-label--required"
>
Номер телефона
</label>
<InputMask
{...input}
type="tel"
name="phone"
id="phone"
mask="+7 (999) 999-99-99"
autoComplete="off"
className={controlClasses}
autoCapitalize="off"
autoCorrect="off"
placeholder="+7 (___) ___-__-__"
required
/>
{meta.touched &&
meta.error && (
<div className="invalid-feedback">{meta.error}</div>
)}
<small
id="nameHelpBlock"
className="form-text form-text--help"
>
Без номера телефона мы не сможем вам позвонить :)
</small>
</FormGroup>
);
}}
</Field>
{values ? (
<pre>{JSON.stringify(values, 0, 2)}</pre>
) : (
<FormSpy subscription={{ values: true }}>
{({ values }) => <pre>{JSON.stringify(values, 0, 2)}</pre>}
</FormSpy>
)}
<FormButtons portalSelector={buttonPortalId}
reset={reset}
submit={handleSubmit}
submitting={submitting}
pristine={pristine} />
</form>
)}
/>
);
}
}
CallbackForm.propTypes = {};
CallbackForm.defaultProps = {};
export default CallbackForm;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment