Created
August 21, 2016 14:59
-
-
Save EnoahNetzach/a953f6b4f35ad7de6cd794b8a85c896d to your computer and use it in GitHub Desktop.
redux-form v5 ArrayField fixer
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, { Component } from 'react' | |
import mapValues from 'lodash/mapValues' | |
import find from 'lodash/find' | |
/* eslint-disable react/prop-types */ | |
/* eslint-disable no-use-before-define */ | |
export default Wrapped => class extends Component { | |
constructor(...args) { | |
super(...args) | |
this.fields = {} | |
} | |
componentWillReceiveProps(nextProps) { | |
if (nextProps.fields !== this.props.fields) { | |
const nextFields = { ...this.fields } | |
const fix = (field, path, decide) => { | |
nextFields[path] = { | |
...this.fields[path], | |
...field.reduce((acc, child, index) => { | |
decide(child, `${path}[${index}]`) | |
return !this.fields[path] || typeof this.fields[path][index] === 'undefined' | |
? { ...acc, [index]: { index, key: index } } | |
: acc | |
}, {}), | |
} | |
} | |
this.fixFieldArray(nextProps.fields, fix) | |
this.fields = nextFields | |
} | |
} | |
fixFieldArray(fieldsToFix, fix) { | |
const decide = (field, path) => (typeof field.name !== 'string' | |
? traverse(field, path) | |
: field | |
) | |
const traverse = (fields, path = 'fields') => (Array.isArray(fields) | |
? fix(fields, path, decide) | |
: mapValues(fields, (field, key) => decide(field, `${path}.${key}`)) | |
) | |
return traverse(fieldsToFix) | |
} | |
render() { | |
const fix = (field, path, decide) => { | |
const fixedField = field | |
.map((child, index) => { | |
if (this.fields[path] && this.fields[path][index] === false) { | |
return undefined | |
} | |
const fixedChild = { | |
...decide(child, `${path}[${index}]`), | |
uniqueKey: this.fields[path][index].key, | |
} | |
Object.getOwnPropertyNames(child) | |
.filter(p => typeof child[p] === 'function') | |
.forEach(fn => { fixedChild[fn] = child[fn] }) | |
return fixedChild | |
}) | |
.filter(child => typeof child !== 'undefined') | |
Object.getOwnPropertyNames(field) | |
.filter(p => typeof field[p] === 'function') | |
.forEach(fn => { fixedField[fn] = field[fn] }) | |
fixedField.addField = () => { | |
this.fields = { | |
...this.fields, | |
[path]: { | |
...this.fields[path], | |
[field.length]: { | |
index: field.length, | |
key: field.length, | |
}, | |
} | |
} | |
return field.addField() | |
} | |
fixedField.removeField = (key) => { | |
const index = find(this.fields[path], item => item.key === key).index | |
this.fields = { | |
...mapValues(this.fields, (stateField, statePath) => ( | |
statePath.startsWith(`${path}[${index}]`) | |
? mapValues(stateField, () => false) | |
: stateField | |
)), | |
[path]: { | |
...this.fields[path], | |
[index]: false, | |
} | |
} | |
this.forceUpdate() | |
} | |
fixedField.swapFields = (key1, key2) => { | |
const index1 = find(this.fields[path], item => item.key === key1).index | |
const index2 = find(this.fields[path], item => item.key === key2).index | |
const invert = (i1, i2) => (stateField, statePath) => { | |
if (!statePath.startsWith(`${path}[${i1}]`)) { | |
return stateField | |
} | |
const replacedPath = statePath.replace(`${path}[${i1}]`, `${path}[${i2}]`) | |
return this.fields[replacedPath] | |
} | |
this.fields = { | |
...mapValues(mapValues(this.fields, invert(index1, index2)), invert(index2, index1)), | |
[path]: { | |
...this.fields[path], | |
[index1]: { | |
index: index1, | |
key: this.fields[path][index2].key, | |
}, | |
[index2]: { | |
index: index2, | |
key: this.fields[path][index1].key, | |
}, | |
} | |
} | |
return field.swapFields(index1, index2) | |
} | |
return fixedField | |
} | |
return ( | |
<Wrapped {...this.props} fields={this.fixFieldArray(this.props.fields, fix)} /> | |
) | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, { PropTypes } from 'react' | |
import { reduxForm } from 'redux-form' | |
import fieldArrayFixer from './fieldArrayFixer' | |
const applyTo = fn => (...args) => () => fn(...args) | |
export const fields = [ | |
'shipping.street', | |
'shipping.city', | |
'shipping.phones[]', | |
'children[].name', | |
'children[].age', | |
'children[].awards[]', | |
] | |
const Form = ({ | |
fields: { | |
shipping: { | |
street, | |
city, | |
phones | |
}, | |
children, | |
}, | |
}) => ( | |
<form> | |
<div> | |
<div> | |
<strong>Street</strong> | |
<input type="text" placeholder="Street" {...street} /> | |
</div> | |
<div> | |
<strong>City</strong> | |
<input type="text" placeholder="City" {...city} /> | |
</div> | |
<div> | |
<button type="button" onClick={applyTo(phones.addField)()}> | |
Add Phone | |
</button> | |
</div> | |
{phones.map((phone, i) => ( | |
<div key={phone.uniqueKey}> | |
<strong>Phone #{i + 1} ({phone.name}) ({phone.uniqueKey})</strong> | |
<input type="text" placeholder="Phone" {...phone} /> | |
<button | |
type="button" | |
onClick={applyTo(phones.removeField)(phone.uniqueKey)} | |
> | |
Remove | |
</button> | |
</div> | |
))} | |
</div> | |
<hl /> | |
<div> | |
<button type="button" onClick={applyTo(children.addField)()}> Add Child | |
</button> | |
</div> | |
{!children.length && <div>No Children</div>} | |
{children.map((child, i) => ( | |
<div key={child.uniqueKey}> | |
<div> | |
<strong>Child #{i + 1} ({child.name.name}) ({child.uniqueKey})</strong> | |
<div> | |
<input type="text" placeholder="Child Name" {...child.name} /> | |
<input type="text" placeholder="Child Age" {...child.age} /> | |
<button | |
type="button" | |
disabled={i === 0} | |
onClick={children[i - 1] | |
? applyTo(children.swapFields)(child.uniqueKey, children[i - 1].uniqueKey) | |
: null | |
} | |
> | |
up {children[i - 1] ? children[i - 1].uniqueKey : ''} | |
</button> | |
<button | |
type="button" | |
disabled={i === children.length - 1} | |
onClick={children[i + 1] | |
? applyTo(children.swapFields)(child.uniqueKey, children[i + 1].uniqueKey) | |
: null | |
} | |
> | |
down {children[i + 1] ? children[i + 1].uniqueKey : ''} | |
</button> | |
<button | |
type="button" | |
onClick={applyTo(children.removeField)(child.uniqueKey)} | |
> | |
Remove ({child.uniqueKey}) | |
</button> | |
</div> | |
<div> | |
<div> | |
<button type="button" onClick={applyTo(child.awards.addField)()}> | |
Add Award ({child.uniqueKey}) | |
</button> | |
</div> | |
{child.awards.map((award, j) => ( | |
<div key={award.uniqueKey}> | |
<strong>Award #{j + 1} ({award.name}) ({award.uniqueKey})</strong> | |
<input type="text" placeholder="Award" {...award} /> | |
<button | |
type="button" | |
onClick={applyTo(child.awards.removeField)(award.uniqueKey)} | |
> | |
Remove | |
</button> | |
</div> | |
))} | |
</div> | |
</div> | |
</div> | |
))} | |
</form> | |
) | |
Form.propTypes = { | |
fields: PropTypes.object | |
} | |
const formProps = { | |
form: 'form', | |
fields | |
} | |
const mapStateToProps = () => ({ | |
initialValues: { | |
shipping: { | |
city: 'NY', | |
phones: [1, 2], | |
} | |
} | |
}) | |
export default reduxForm( | |
formProps, | |
mapStateToProps, | |
)(fieldArrayFixer(Form)) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment