Skip to content

Instantly share code, notes, and snippets.

@EnoahNetzach
Created August 21, 2016 14:59
Show Gist options
  • Save EnoahNetzach/a953f6b4f35ad7de6cd794b8a85c896d to your computer and use it in GitHub Desktop.
Save EnoahNetzach/a953f6b4f35ad7de6cd794b8a85c896d to your computer and use it in GitHub Desktop.
redux-form v5 ArrayField fixer
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)} />
)
}
}
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