Skip to content

Commit

Permalink
Avoid React rebuilding DOM for redux-form nested components.
Browse files Browse the repository at this point in the history
  • Loading branch information
jordanh committed Jun 29, 2016
1 parent 6ed6934 commit 530aa57
Show file tree
Hide file tree
Showing 4 changed files with 157 additions and 86 deletions.
90 changes: 62 additions & 28 deletions src/universal/components/Field/Field.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@ const fieldLightGray = appTheme.palette.dark50l;

let styles = {};

const Field = props => {
/*
* Why are we defining this here?
* See: https://github.com/erikras/redux-form/releases/tag/v6.0.0-alpha.14
*/
const FieldBlock = props => {
const {
buttonDisabled,
buttonIcon,
Expand All @@ -20,7 +24,6 @@ const Field = props => {
helpText,
isLarger,
isWider,
name,
onButtonClick,
shortcutHint,
theme,
Expand All @@ -46,36 +49,67 @@ const Field = props => {

const fieldStyles = combineStyles.apply('null', styleOptions);

return (
<div className={styles.fieldBlock}>
<input
className={fieldStyles}
{...props}
/>
{hasButton &&
<div className={styles.buttonBlock}>
<IconButton
disabled={buttonDisabled}
iconName={buttonIcon}
iconSize="2x"
onClick={onButtonClick}
/>
</div>
}
{hasHelpText &&
<div className={helpTextStyles}>{helpText}</div>
}
{hasShortcutHint &&
<div className={shortcutHintStyles}>{shortcutHint}</div>
}
</div>
);
};

FieldBlock.propTypes = {
autoFocus: PropTypes.bool,
buttonDisabled: PropTypes.bool,
buttonIcon: PropTypes.string,
hasButton: PropTypes.bool,
hasErrorText: PropTypes.bool,
hasHelpText: PropTypes.bool,
hasShortcutHint: PropTypes.bool,
helpText: PropTypes.object,
isLarger: PropTypes.bool,
isWider: PropTypes.bool,
onBlur: PropTypes.func,
onButtonClick: PropTypes.func,
onChange: PropTypes.func,
onFocus: PropTypes.func,
placeholder: PropTypes.string,
shortcutHint: PropTypes.string,
theme: PropTypes.oneOf([
'cool',
'warm'
]),
type: PropTypes.string,
value: PropTypes.string
};

const Field = props => {
const {
name,
} = props;

return (
<div className={styles.fieldBlock}>
<ReduxFormField
name={name}
component={nestedProps => {
return (
<div className={styles.fieldBlock}>
<input
className={fieldStyles}
{...nestedProps}
/>
{hasButton &&
<div className={styles.buttonBlock}>
<IconButton
disabled={buttonDisabled}
iconName={buttonIcon}
iconSize="2x"
onClick={onButtonClick}
/>
</div>
}
{hasHelpText &&
<div className={helpTextStyles}>{helpText}</div>
}
{hasShortcutHint &&
<div className={shortcutHintStyles}>{shortcutHint}</div>
}
</div>
);
}}
component={FieldBlock}
{...props}
/>
</div>
Expand Down
138 changes: 86 additions & 52 deletions src/universal/components/LabeledFieldArray/LabeledFieldArray.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,85 @@ import IconButton from 'universal/components/IconButton/IconButton';
const combineStyles = StyleSheet.combineStyles;
let styles = {};

/*
* Why are we defining this here?
* See: https://github.com/erikras/redux-form/releases/tag/v6.0.0-alpha.14
*/
const FieldsBlock = props => {
const {
labelGetter,
labelHeader,
fields,
hoverRow,
nestedFieldHeader,
nestedFieldName,
onHoverRow,
onLeaveRow
} = props;

const columnLeftStyles = combineStyles(styles.fieldGroupColumn, styles.fieldGroupColumnLeft);
const columnRightStyles = combineStyles(styles.fieldGroupColumn, styles.fieldGroupColumnRight);
const fieldLabelStyles = combineStyles(styles.fieldGroupLabel, styles.fieldGroupLabelForFields);

return (
<div className={styles.fieldGroup}>
<div className={styles.fieldGroupRow}>
<div className={columnLeftStyles}>
<div className={styles.fieldGroupLabel}>
{labelHeader}
</div>
</div>
<div className={columnRightStyles}>
<div className={fieldLabelStyles}>
{nestedFieldHeader}
</div>
</div>
</div>
{fields.map((item, index) =>
<div
className={styles.fieldGroupRow}
key={index}
onMouseEnter={() => onHoverRow(index)}
onMouseLeave={() => onLeaveRow()}
>
<div className={columnLeftStyles}>
<div className={styles.fieldRemovalBlock}>
{(hoverRow === index) && <IconButton
iconName="times-circle"
iconSize="2x"
onClick={() => fields.remove(index)}
title="Remove"
/>}
</div>
<div className={styles.fieldLabel}>
{labelGetter(index)}
</div>
</div>
<div className={columnRightStyles}>
<Field
name={`${item}.${nestedFieldName}`}
placeholder="What’s their priority this week?"
type="text"
/>
</div>
</div>
)}
</div>
);
};

FieldsBlock.propTypes = {
labelGetter: PropTypes.func.isRequired,
labelHeader: PropTypes.string.isRequired,
fields: PropTypes.object.isRequired,
hoverRow: PropTypes.number,
nestedFieldHeader: PropTypes.string.isRequired,
nestedFieldName: PropTypes.string.isRequired,
onHoverRow: PropTypes.func.isRequired,
onLeaveRow: PropTypes.func.isRequired
};


@look
export default class LabeledFieldArray extends Component {
static propTypes = {
Expand All @@ -20,6 +99,8 @@ export default class LabeledFieldArray extends Component {

constructor(props) {
super(props);
this.onHoverRow = this.onHoverRow.bind(this);
this.onLeaveRow = this.onLeaveRow.bind(this);
this.state = {
hoverRow: null
};
Expand All @@ -30,67 +111,20 @@ export default class LabeledFieldArray extends Component {

render() {
const {
labelGetter,
labelHeader,
labelSource,
nestedFieldHeader,
nestedFieldName
} = this.props;

const {hoverRow} = this.state;

const columnLeftStyles = combineStyles(styles.fieldGroupColumn, styles.fieldGroupColumnLeft);
const columnRightStyles = combineStyles(styles.fieldGroupColumn, styles.fieldGroupColumnRight);
const fieldLabelStyles = combineStyles(styles.fieldGroupLabel, styles.fieldGroupLabelForFields);

return (
<FieldArray
name={labelSource}
component={reduxFormFieldArray =>
<div className={styles.fieldGroup}>
<div className={styles.fieldGroupRow}>
<div className={columnLeftStyles}>
<div className={styles.fieldGroupLabel}>
{labelHeader}
</div>
</div>
<div className={columnRightStyles}>
<div className={fieldLabelStyles}>
{nestedFieldHeader}
</div>
</div>
</div>
{reduxFormFieldArray.fields.map((item, index) =>
<div
className={styles.fieldGroupRow}
key={index}
onMouseEnter={() => this.onHoverRow(index)}
onMouseLeave={() => this.onLeaveRow()}
>
<div className={columnLeftStyles}>
<div className={styles.fieldRemovalBlock}>
{(hoverRow === index) && <IconButton
iconName="times-circle"
iconSize="2x"
onClick={() => reduxFormFieldArray.fields.remove(index)}
title="Remove"
/>}
</div>
<div className={styles.fieldLabel}>
{labelGetter(index)}
</div>
</div>
<div className={columnRightStyles}>
<Field
name={`${item}.${nestedFieldName}`}
placeholder="What’s their priority this week?"
type="text"
/>
</div>
</div>
)}
</div>
}
component={FieldsBlock}
hoverRow={hoverRow}
onHoverRow={this.onHoverRow}
onLeaveRow={this.onLeaveRow}
{...this.props}
/>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export default class Step3InviteTeam extends Component {
invitees: PropTypes.array,
inviteesRaw: PropTypes.string,
onSubmit: PropTypes.func,
submitting: PropTypes.bool,
teamName: PropTypes.string.isRequired
}

Expand All @@ -45,15 +46,15 @@ export default class Step3InviteTeam extends Component {
}
parsedAddresses.forEach(email => {
dispatch(arrayPush('welcomeWizard', 'invitees', {
address: email.address,
name: email.name,
email: email.address,
fullName: email.name,
label: email.name ? `"${email.name}" <${email.address}>` : email.address
}));
});
}

render() {
const {handleSubmit, invitees, inviteesRaw, teamName} = this.props;
const {handleSubmit, invitees, inviteesRaw, submitting, teamName} = this.props;

const invitesFieldHasError = false; // TODO: wire this up for real
const helpText = invitesFieldHasError ?
Expand All @@ -77,7 +78,7 @@ export default class Step3InviteTeam extends Component {
<WelcomeHeading copy={<span>Let’s invite some folks to the <b>{teamName}</b> team.</span>} />
<HotKeys handlers={{ keyEnter: this.onAddInviteesButtonClick}}>
<Field
autoFocus
autoFocus={!invitees || invitees.length === 0}
buttonDisabled={!inviteesRaw}
buttonIcon="check-circle"
hasButton
Expand All @@ -97,8 +98,9 @@ export default class Step3InviteTeam extends Component {
labelHeader="Invitee"
labelSource="invitees"
nestedFieldHeader="This Week's Priority"
nestedFieldName="outcome"
nestedFieldName="task"
/>
<button type="submit" disabled={submitting}>Submit</button>
</form>
</HotKeys>
</div>
Expand Down
3 changes: 2 additions & 1 deletion src/universal/modules/welcome/containers/Welcome/Welcome.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,14 +111,15 @@ export default class WelcomeContainer extends Component {

onInviteTeamSubmit = data => {
const {dispatch, welcome: {teamId}} = this.props;
debugger;
const {invitees} = data;
const options = {
variables: {
teamId,
invitees
}
};
cashay.mutate('inviteTeam', options)
cashay.mutate('inviteTeamMembers', options)
.then(res => {
// TODO make sure this resolves after the route changes
console.log('inviteTeamRes', res);
Expand Down

0 comments on commit 530aa57

Please sign in to comment.