#100: form loading state for profile

This commit is contained in:
SleepWalker 2016-05-27 23:04:17 +03:00
parent 808f239286
commit c51bd17259
9 changed files with 78 additions and 18 deletions

View File

@ -22,6 +22,7 @@
"intl": "^1.2.2", "intl": "^1.2.2",
"intl-format-cache": "^2.0.4", "intl-format-cache": "^2.0.4",
"intl-messageformat": "^1.1.0", "intl-messageformat": "^1.1.0",
"promise.prototype.finally": "^1.0.1",
"react": "^15.0.0", "react": "^15.0.0",
"react-dom": "^15.0.0", "react-dom": "^15.0.0",
"react-helmet": "^2.3.1", "react-helmet": "^2.3.1",

View File

@ -108,6 +108,7 @@ export default class ChangeEmail extends Component {
<Button <Button
color="violet" color="violet"
type="submit"
block block
label={this.isLastStep() ? messages.changeEmailButton : messages.sendEmailButton} label={this.isLastStep() ? messages.changeEmailButton : messages.sendEmailButton}
onClick={this.onSubmit} onClick={this.onSubmit}

View File

@ -88,7 +88,7 @@ export default class ChangePassword extends Component {
</div> </div>
</div> </div>
<Button color="green" block label={messages.changePasswordButton} /> <Button color="green" block label={messages.changePasswordButton} type="submit" />
</div> </div>
</div> </div>
</Form> </Form>

View File

@ -1,7 +1,6 @@
import React, { Component, PropTypes } from 'react'; import React, { Component, PropTypes } from 'react';
import { FormattedMessage as Message } from 'react-intl'; import { FormattedMessage as Message } from 'react-intl';
import { Link } from 'react-router';
import Helmet from 'react-helmet'; import Helmet from 'react-helmet';
import { Input, Button, Form, FormModel } from 'components/ui/form'; import { Input, Button, Form, FormModel } from 'components/ui/form';
@ -69,7 +68,7 @@ export default class ChangeUsername extends Component {
</div> </div>
</div> </div>
<Button color="green" block label={messages.changeUsernameButton} /> <Button color="green" block label={messages.changeUsernameButton} type="submit" />
</div> </div>
</div> </div>
</Form> </Form>

View File

@ -18,7 +18,7 @@ export default class PasswordRequestForm extends Component {
const {form} = this.props; const {form} = this.props;
return ( return (
<Form onSubmit={this.onSubmit} <Form onSubmit={this.onFormSubmit}
form={form} form={form}
> >
<h2> <h2>
@ -34,12 +34,12 @@ export default class PasswordRequestForm extends Component {
icon="key" icon="key"
placeholder={messages.pleaseEnterPassword} placeholder={messages.pleaseEnterPassword}
/> />
<Button color="green" label="OK" block /> <Button color="green" label="OK" block type="submit" />
</Form> </Form>
); );
} }
onSubmit = () => { onFormSubmit = () => {
this.props.onSubmit(this.props.form); this.props.onSubmit(this.props.form);
}; };
} }

View File

@ -29,19 +29,42 @@ export default class Form extends Component {
}; };
state = { state = {
isTouched: false isTouched: false,
isLoading: this.props.isLoading || false
}; };
componentWillMount() {
if (this.props.form) {
this.props.form.addLoadingListener(this.onLoading);
}
}
componentWillReceiveProps(nextProps) { componentWillReceiveProps(nextProps) {
if (nextProps.id !== this.props.id) { if (nextProps.id !== this.props.id) {
this.setState({ this.setState({
isTouched: false isTouched: false
}); });
} }
if (typeof nextProps.isLoading !== 'undefined') {
this.setState({
isLoading: nextProps.isLoading
});
}
if (nextProps.form && nextProps.form !== this.props.form) {
throw new Error('The FormModel instance should not be changed during component lifetime');
}
}
componentWillUnmount() {
if (this.props.form) {
this.props.form.removeLoadingListener(this.onLoading);
}
} }
render() { render() {
const {isLoading} = this.props; const {isLoading} = this.state;
return ( return (
<form <form
@ -100,4 +123,6 @@ export default class Form extends Component {
this.props.onInvalid(errors); this.props.onInvalid(errors);
} }
}; };
onLoading = (isLoading) => this.setState({isLoading});
} }

View File

@ -3,6 +3,7 @@ import FormInputComponent from './FormInputComponent';
export default class FormModel { export default class FormModel {
fields = {}; fields = {};
errors = {}; errors = {};
handlers = [];
/** /**
* Connects form with React's component * Connects form with React's component
@ -83,4 +84,31 @@ export default class FormModel {
return acc; return acc;
}, {}); }, {});
} }
/**
* Bind handler to listen for form loading state change
*
* @param {Function} fn
*/
addLoadingListener(fn) {
this.removeLoadingListener(fn);
this.handlers.push(fn);
}
/**
* Remove form loading state handler
*
* @param {Function} fn
*/
removeLoadingListener(fn) {
this.handlers = this.handlers.filter((handler) => handler !== fn);
}
beginLoading() {
this.handlers.forEach((fn) => fn(true));
}
endLoading() {
this.handlers.forEach((fn) => fn(false));
}
} }

View File

@ -1,4 +1,5 @@
import 'babel-polyfill'; import 'babel-polyfill';
import 'promise.prototype.finally';
import React from 'react'; import React from 'react';
import ReactDOM from 'react-dom'; import ReactDOM from 'react-dom';

View File

@ -50,8 +50,9 @@ export default connect(null, {
return routeActions.push('/'); return routeActions.push('/');
}, },
fetchUserData, fetchUserData,
onSubmit: ({form, sendData}) => (dispatch) => onSubmit: ({form, sendData}) => (dispatch) => {
sendData() form.beginLoading();
return sendData()
.catch((resp) => { .catch((resp) => {
const requirePassword = resp.errors && !!resp.errors.password; const requirePassword = resp.errors && !!resp.errors.password;
@ -73,6 +74,7 @@ export default connect(null, {
dispatch(createPopup(PasswordRequestForm, (props) => ({ dispatch(createPopup(PasswordRequestForm, (props) => ({
form, form,
onSubmit: () => { onSubmit: () => {
form.beginLoading();
sendData() sendData()
.catch((resp) => { .catch((resp) => {
if (resp.errors) { if (resp.errors) {
@ -82,11 +84,14 @@ export default connect(null, {
return Promise.reject(resp); return Promise.reject(resp);
}) })
.then(resolve) .then(resolve)
.then(props.onClose); .then(props.onClose)
.finally(() => form.endLoading());
} }
}))); })));
} else { } else {
resolve(); resolve();
} }
})) }))
.finally(() => form.endLoading());
}
})(ProfilePage); })(ProfilePage);