Skip to content

Commit

Permalink
higher order components + refac
Browse files Browse the repository at this point in the history
  • Loading branch information
dzikowski committed Jun 3, 2017
1 parent b68aa1f commit a1802a7
Show file tree
Hide file tree
Showing 16 changed files with 213 additions and 207 deletions.
3 changes: 2 additions & 1 deletion .eslintrc.js
Expand Up @@ -8,6 +8,7 @@ module.exports = {
"parser": "babel-eslint",
"rules": {
// disable detecting of unused PropTypes because it leads to many false positives for HoCs
"react/no-unused-prop-types": 0
"react/no-unused-prop-types": 0,
"arrow-parens": ["error", "always"]
}
};
2 changes: 1 addition & 1 deletion app/App.jsx
Expand Up @@ -13,7 +13,7 @@ class App extends Component {
};

auth = new Auth({
updateUser: user => this.setState({ ...this.state, user }),
updateUser: (user) => this.setState({ ...this.state, user }),
});

render() {
Expand Down
33 changes: 25 additions & 8 deletions app/components/messages.jsx
Expand Up @@ -11,42 +11,59 @@ const messageStyles = {
loading: {
color: 'gray',
},
success: {
color: 'forestgreen',
margin: '1em 0',
},
loadingIcon: {
width: '2em',
height: '2em',
},
messageIcon: {
float: 'left',
marginTop: '-0.2em',
marginTop: '-0.3em',
fontSize: '2em',
},
messageText: {
marginLeft: '2.5em',
},
};

export function ErrorMessage({ text }) {
export function ErrorMessage({ children }) {
return (
<div style={messageStyles.error}>
<FontIcon style={messageStyles.messageIcon}>error_outline</FontIcon>
<div style={messageStyles.messageText}>{text}</div>
<FontIcon style={messageStyles.messageIcon}>priority_high</FontIcon>
<div style={messageStyles.messageText}>{children}</div>
</div>
);
}

ErrorMessage.propTypes = {
text: PropTypes.string.isRequired,
children: PropTypes.element.isRequired,
};

export function Loader({ text }) {
export function Loader({ children }) {
return (
<div style={messageStyles.loading}>
<ProgressBar type="linear" mode="indeterminate" />
<div>{text}</div>
<div>{children}</div>
</div>
);
}

Loader.propTypes = {
text: PropTypes.string.isRequired,
children: PropTypes.element.isRequired,
};

export function SuccessMessage({ children }) {
return (
<div style={messageStyles.success}>
<FontIcon style={messageStyles.messageIcon}>check</FontIcon>
<div style={messageStyles.messageText}>{children}</div>
</div>
);
}

SuccessMessage.propTypes = {
children: PropTypes.element.isRequired,
};
2 changes: 1 addition & 1 deletion app/components/routes.jsx
Expand Up @@ -6,7 +6,7 @@ import { User } from '../services/auth';
const mergedProps = (...props) => Object.assign({}, ...props);

export function DefaultRoute({ component: Component, ...rest }) {
return <Route {...rest} render={props => <Component {...mergedProps(rest, props)} />} />;
return <Route {...rest} render={(props) => <Component {...mergedProps(rest, props)} />} />;
}

DefaultRoute.propTypes = {
Expand Down
3 changes: 2 additions & 1 deletion app/components/withFormFields/FormFields.js
@@ -1,6 +1,7 @@
export default class FormFields {
constructor(values, handleChange) {
constructor(values, handleChange, reset) {
this.values = values;
this.handleChange = handleChange;
this.reset = reset;
}
}
6 changes: 4 additions & 2 deletions app/components/withFormFields/withFormFields.jsx
Expand Up @@ -8,13 +8,15 @@ export default function withFormFields(WrappedComponent, getDefaultValues) {
fields: getDefaultValues(this.props),
};

handleChange = name => value => {
handleChange = (name) => (value) => {
const fields = { ...this.state.fields, [name]: value };
this.setState({ fields });
};

reset = () => this.setState({ fields: getDefaultValues(this.props) });

render() {
const fields = new FormFields(this.state.fields, this.handleChange);
const fields = new FormFields(this.state.fields, this.handleChange, this.reset);
return <WrappedComponent fields={fields} {...this.props} />;
}
}
Expand Down
4 changes: 2 additions & 2 deletions app/components/withFormState/FormState.js
@@ -1,6 +1,6 @@
export default class FormState {
constructor({ startLoading, handleFailure, handleSuccess, infoComponent }) {
this.startLoading = startLoading;
constructor({ submit, handleFailure, handleSuccess, infoComponent }) {
this.submit = submit;
this.handleFailure = handleFailure;
this.handleSuccess = handleSuccess;
this.infoComponent = infoComponent;
Expand Down
8 changes: 4 additions & 4 deletions app/components/withFormState/FormStateInfo.jsx
@@ -1,11 +1,11 @@
import React from 'react';
import PropTypes from 'prop-types';
import { ErrorMessage, Loader } from './../messages';
import { ErrorMessage, Loader, SuccessMessage } from './../messages';

export default function FormStateInfo({ loading, error, success }) {
if (loading) { return <Loader text={loading} />; }
if (error) { return <ErrorMessage text={error} />; }
if (success) { return <div>{success}</div>; }
if (loading) { return <Loader >{loading}</Loader>; }
if (error) { return <ErrorMessage>{error}</ErrorMessage>; }
if (success) { return <SuccessMessage> {success}</SuccessMessage>; }
return <div />;
}

Expand Down
11 changes: 7 additions & 4 deletions app/components/withFormState/withFormState.jsx
Expand Up @@ -11,15 +11,18 @@ export default function withFormState(WrappedComponent) {
success: null,
};

handleFailure = info => this.setState({ ...this.state, error: info, loading: null });
handleFailure = (info) => this.setState({ loading: null, error: info, success: null });

handleSuccess = info => this.setState({ ...this.state, success: info, loading: null });
handleSuccess = (info) => this.setState({ loading: null, error: null, success: info });

startLoading = info => this.setState({ ...this.state, loading: info });
submit = (operation, info) => {
this.setState({ ...this.state, loading: info });
return operation();
};

render() {
const form = new FormState({
startLoading: this.startLoading,
submit: this.submit,
handleFailure: this.handleFailure,
handleSuccess: this.handleSuccess,
infoComponent: <FormStateInfo {...this.state} />,
Expand Down
136 changes: 66 additions & 70 deletions app/scenes/profile/ConfirmRegistration.jsx
Expand Up @@ -3,93 +3,89 @@ import PropTypes from 'prop-types';
import { Redirect } from 'react-router-dom';
import { Button } from 'react-toolbox/lib/button';
import { Input } from 'react-toolbox/lib/input';
import { withFormState, FormState } from '../../components/withFormState';
import { FormFields, withFormFields } from '../../components/withFormFields';
import { FormState, withFormState } from '../../components/withFormState';
import { Auth, User } from '../../services/auth';

class ConfirmRegistration extends React.Component {

state = this.initialState();

initialState() {
return {
email: this.props.user.email || '',
code: '',
};
}

handleChange = name => value => this.setState({ ...this.state, [name]: value });

handleRegistrationConfirmation = () => {
const onSuccess = () => {
this.setState(this.initialState());
this.props.form.handleSuccess(<Redirect push to="/profile/sign-in" />);
};

const onFailure = (error) => {
this.props.form.handleFailure(error.message);
};

this.props.form.startLoading('Confirming registration...');
function confirmRegistration({ auth, form, fields }) {
const onSuccess = () => {
fields.reset();
form.handleSuccess(<Redirect push to="/profile/sign-in" />);
};

this.props.auth.confirmRegistration({
email: this.state.email,
code: this.state.code,
const action = () =>
auth.confirmRegistration({
email: fields.values.email,
code: fields.values.code,
onSuccess,
onFailure,
onFailure: (error) => form.handleFailure(error.message),
});
};

handleRequestingCodeAgain = () => {
const onSuccess = () => {
this.setState({ ...this.state, code: '' });
this.props.form.handleSuccess();
};

const onFailure = (error) => {
this.props.form.handleFailure(error.message);
};
form.submit(action, 'Confirming registration...');
}

this.props.form.startLoading('Sending confirmation code...');
function requestCodeAgain({ auth, form, fields }) {
const onSuccess = () => {
const message = `Confirmation code has been sent to ${fields.values.email}`;
fields.reset();
form.handleSuccess(message);
};

this.props.auth.requestCodeAgain({
email: this.state.email,
const action = () =>
auth.requestCodeAgain({
email: fields.values.email,
onSuccess,
onFailure,
onFailure: (error) => form.handleFailure(error.message),
});
};

render() {
return (
<div>
<h1>Confirm registration</h1>
{this.props.form.infoComponent}
<Input
type="text"
label="E-mail Address"
name="email"
value={this.state.email}
onChange={this.handleChange('email')}
/>
<Input
type="text"
label="Confirmation code"
name="code"
value={this.state.code}
onChange={this.handleChange('code')}
/>
<Button label="Confirm registration" onClick={this.handleRegistrationConfirmation} raised primary />
&nbsp;
<Button label="Request code again" onClick={this.handleRequestingCodeAgain} />
</div>
);
}
form.submit(action, 'Sending confirmation code...');
}


function ConfirmRegistration({ auth, form, fields }) {
return (
<div>
<h1>Confirm registration</h1>
{form.infoComponent}
<Input
type="text"
label="E-mail Address"
name="email"
value={fields.values.email}
onChange={fields.handleChange('email')}
/>
<Input
type="text"
label="Confirmation code"
name="code"
value={fields.values.code}
onChange={fields.handleChange('code')}
/>
<Button
label="Confirm registration"
onClick={() => confirmRegistration({ auth, form, fields })}
raised
primary
/>
&nbsp;
<Button
label="Request code again"
onClick={() => requestCodeAgain({ auth, form, fields })}
/>
</div>
);
}

ConfirmRegistration.propTypes = {
auth: PropTypes.instanceOf(Auth).isRequired,
fields: PropTypes.instanceOf(FormFields).isRequired,
form: PropTypes.instanceOf(FormState).isRequired,
user: PropTypes.instanceOf(User).isRequired,
};

const ConfirmRegistrationExt = withFormState(ConfirmRegistration);
const ConfirmRegistrationExt = withFormState(withFormFields(
ConfirmRegistration,
({ user }) => ({ email: user.email || '', code: '' }),
));

export default ConfirmRegistrationExt;

0 comments on commit a1802a7

Please sign in to comment.