Skip to content

Commit

Permalink
Breaking change: Change Fields names instance API to return FormSecti…
Browse files Browse the repository at this point in the history
…on prefixed names, to be consistent with Field and FieldArray. (#2464)

Breaking change: Change instance API documentation of Field, Fields and FieldArray name and names to match the behavior of the code with regards to FormSection, which is to return the FormSection prefixed name and names.
In FieldArray, fields map, forEach and reduce now call back with unprefixed name, as the prefix is added in Field and Fields. Fixes #2121.
  • Loading branch information
huan086 authored and erikras committed Jan 20, 2017
1 parent fc363d7 commit 162d45d
Show file tree
Hide file tree
Showing 12 changed files with 246 additions and 25 deletions.
6 changes: 4 additions & 2 deletions docs/api/Field.md
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,8 @@ The following properties and methods are available on an instance of a `Field` c
#### `name : String`

> The `name` prop that you passed in.
> When nested in `FormSection`, returns the `name` prop prefixed with the `FormSection` name.
Otherwise, returns the `name` prop that you passed in.

#### `pristine : boolean`

Expand Down Expand Up @@ -259,7 +260,8 @@ to be destructured into your `<input/>` component.
#### `input.name : String`

> The name prop passed in.
> When nested in `FormSection`, returns the `name` prop prefixed with the `FormSection` name.
Otherwise, returns the `name` prop that you passed in.

#### `input.onBlur(eventOrValue) : Function`

Expand Down
3 changes: 2 additions & 1 deletion docs/api/FieldArray.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@ The following properties and methods are available on an instance of a `FieldArr

#### `name : String`

> The `name` prop that you passed in.
> When nested in `FormSection`, returns the `name` prop prefixed with the `FormSection` name.
Otherwise, returns the `name` prop that you passed in.

#### `valid : boolean`

Expand Down
3 changes: 2 additions & 1 deletion docs/api/Fields.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,8 @@ The following properties and methods are available on an instance of a `Field` c

#### `names : Array<String>`

> The `names` prop that you passed in.
> When nested in `FormSection`, returns the `names` prop prefixed with the `FormSection` name.
Otherwise, returns a copy of the `names` prop that you passed in.

#### `pristine : boolean`

Expand Down
3 changes: 2 additions & 1 deletion src/ConnectedFieldArray.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,12 +69,13 @@ const createConnectedFieldArray = ({ deepEqual, getIn, size }) => {
component,
withRef,
name,
_reduxForm, // eslint-disable-line no-unused-vars
_reduxForm,
...rest
} = this.props
const props = createFieldArrayProps(
getIn,
name,
_reduxForm.sectionPrefix,
this.getValue,
{
...rest,
Expand Down
5 changes: 3 additions & 2 deletions src/Fields.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ const createFields = ({ deepEqual, getIn, toJS }) => {
}
const { context } = this
const { _reduxForm: { register } } = context
this.names.forEach(name => register(prefixName(context, name), 'Field'))
this.names.forEach(name => register(name, 'Field'))
}

componentWillReceiveProps(nextProps) {
Expand Down Expand Up @@ -69,7 +69,8 @@ const createFields = ({ deepEqual, getIn, toJS }) => {
}

get names() {
return this.props.names
const { context } = this
return this.props.names.map(name => prefixName(context, name))
}

get dirty() {
Expand Down
39 changes: 39 additions & 0 deletions src/__tests__/Field.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -589,6 +589,45 @@ const describeField = (name, structure, combineReducers, expect) => {
expect(input.calls[ 1 ].arguments[ 0 ].meta.touched).toBe(true)
})

it('should prefix name getter when inside FormSection', () => {
const store = makeStore()
class Form extends Component {
render() {
return (<FormSection name="foo" component="span">
<Field name="bar" component="input"/>
</FormSection>)
}
}
const TestForm = reduxForm({ form: 'testForm' })(Form)
const dom = TestUtils.renderIntoDocument(
<Provider store={store}>
<TestForm/>
</Provider>
)
const stub = TestUtils.findRenderedComponentWithType(dom, Field)
expect(stub.name).toBe('foo.bar')
})
it('should prefix name getter when inside multiple FormSection', () => {
const store = makeStore()
class Form extends Component {
render() {
return (<FormSection name="foo">
<FormSection name="fighter">
<Field name="bar" component="input"/>
</FormSection>
</FormSection>)
}
}
const TestForm = reduxForm({ form: 'testForm' })(Form)
const dom = TestUtils.renderIntoDocument(
<Provider store={store}>
<TestForm/>
</Provider>
)
const stub = TestUtils.findRenderedComponentWithType(dom, Field)
expect(stub.name).toBe('foo.fighter.bar')
})

it('should prefix name when inside FormSection', () => {
const store = makeStore()
class Form extends Component {
Expand Down
140 changes: 140 additions & 0 deletions src/__tests__/FieldArray.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import createReducer from '../reducer'
import createFieldArray from '../FieldArray'
import createField from '../Field'
import createFields from '../Fields'
import FormSection from '../FormSection'
import plain from '../structure/plain'
import plainExpectations from '../structure/plain/expectations'
import immutable from '../structure/immutable'
Expand Down Expand Up @@ -668,6 +669,145 @@ const describeFieldArray = (name, structure, combineReducers, expect) => {
expect(component.calls[ 1 ].arguments[ 0 ].fields.length).toBe(1)
})

it('should not prefix name in fields map callback when inside FormSection', () => {
const store = makeStore({
testForm: {
values: {
foo: { bar: [ { val: 'dog' }, { val: 'cat' } ] }
}
}
})
const TestArray = ({ fields }) => (<div>{fields.map(name => <Field name={`${name}.val`} component={TestComponent} />)}</div>)
class Form extends Component {
render() {
return (<FormSection name="foo">
<FieldArray name="bar" component={TestArray} />
</FormSection>)
}
}
const TestForm = reduxForm({ form: 'testForm' })(Form)
const dom = TestUtils.renderIntoDocument(
<Provider store={store}>
<TestForm/>
</Provider>
)
expect(store.getState()).toEqualMap({
form: {
testForm: {
registeredFields: [
{ name: 'foo.bar', type: 'FieldArray' },
{ name: 'foo.bar[0].val', type: 'Field' },
{ name: 'foo.bar[1].val', type: 'Field' }
],
values: {
foo: { bar: [ { val: 'dog' }, { val: 'cat' } ] }
}
}
}
})

const components = TestUtils.scryRenderedComponentsWithType(dom, TestComponent)
expect(components[0].props.input.name).toBe('foo.bar[0].val')
expect(components[1].props.input.name).toBe('foo.bar[1].val')
})
it('should prefix name getter when inside FormSection', () => {
const store = makeStore({
testForm: {
values: {
foo: { bar: [ { val: 'dog' }, { val: 'cat' } ] }
}
}
})
const TestArray = ({ fields }) => (<div>{fields.map(name => <Field name={`${name}.val`} component={TestComponent} />)}</div>)
class Form extends Component {
render() {
return (<FormSection name="foo">
<FieldArray name="bar" component={TestArray} />
</FormSection>)
}
}
const TestForm = reduxForm({ form: 'testForm' })(Form)
const dom = TestUtils.renderIntoDocument(
<Provider store={store}>
<TestForm/>
</Provider>
)
const stub = TestUtils.findRenderedComponentWithType(dom, FieldArray)
expect(stub.name).toBe('foo.bar')
})

it('should not prefix name in fields map callback when inside multiple FormSection', () => {
const store = makeStore({
testForm: {
values: {
foo: { fighter: { bar: [ { val: 'dog' }, { val: 'cat' } ] } }
}
}
})
const TestArray = ({ fields }) => (<div>{fields.map(name => <Field name={`${name}.val`} component={TestComponent} />)}</div>)
class Form extends Component {
render() {
return (<FormSection name="foo">
<FormSection name="fighter">
<FieldArray name="bar" component={TestArray} />
</FormSection>
</FormSection>)
}
}
const TestForm = reduxForm({ form: 'testForm' })(Form)
const dom = TestUtils.renderIntoDocument(
<Provider store={store}>
<TestForm/>
</Provider>
)

expect(store.getState()).toEqualMap({
form: {
testForm: {
registeredFields: [
{ name: 'foo.fighter.bar', type: 'FieldArray' },
{ name: 'foo.fighter.bar[0].val', type: 'Field' },
{ name: 'foo.fighter.bar[1].val', type: 'Field' }
],
values: {
foo: { fighter: { bar: [ { val: 'dog' }, { val: 'cat' } ] } }
}
}
}
})

const components = TestUtils.scryRenderedComponentsWithType(dom, TestComponent)
expect(components[0].props.input.name).toBe('foo.fighter.bar[0].val')
expect(components[1].props.input.name).toBe('foo.fighter.bar[1].val')
})
it('should prefix name getter when inside multiple FormSection', () => {
const store = makeStore({
testForm: {
values: {
foo: { fighter: { bar: [ { val: 'dog' }, { val: 'cat' } ] } }
}
}
})
const TestArray = ({ fields }) => (<div>{fields.map(name => <Field name={`${name}.val`} component={TestComponent} />)}</div>)
class Form extends Component {
render() {
return (<FormSection name="foo">
<FormSection name="fighter">
<FieldArray name="bar" component={TestArray} />
</FormSection>
</FormSection>)
}
}
const TestForm = reduxForm({ form: 'testForm' })(Form)
const dom = TestUtils.renderIntoDocument(
<Provider store={store}>
<TestForm/>
</Provider>
)
const stub = TestUtils.findRenderedComponentWithType(dom, FieldArray)
expect(stub.name).toBe('foo.fighter.bar')
})

it('should provide field-level sync error for array field', () => {
const store = makeStore({
testForm: {
Expand Down
46 changes: 46 additions & 0 deletions src/__tests__/Fields.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -678,6 +678,52 @@ const describeFields = (name, structure, combineReducers, expect) => {
expect(input.calls[ 1 ].arguments[ 0 ].bar.meta.touched).toBe(true)
})

it('should prefix name getter when inside FormSection', () => {
const store = makeStore()
const renderFields = ({ foo, bar }) => <div>
<input {...foo.input}/>
<input {...bar.input}/>
</div>
class Form extends Component {
render() {
return (<FormSection name="foo">
<Fields names={[ 'foo', 'bar' ]} component={renderFields}/>
</FormSection>)
}
}
const TestForm = reduxForm({ form: 'testForm' })(Form)
const dom = TestUtils.renderIntoDocument(
<Provider store={store}>
<TestForm/>
</Provider>
)
const stub = TestUtils.findRenderedComponentWithType(dom, Fields)
expect(stub.names).toEqual([ 'foo.foo', 'foo.bar' ])
})
it('should prefix name getter when inside multiple FormSection', () => {
const store = makeStore()
const renderFields = ({ foo, bar }) => <div>
<input {...foo.input}/>
<input {...bar.input}/>
</div>
class Form extends Component {
render() {
return (<FormSection name="foo">
<FormSection name="fighter">
<Fields names={[ 'foo', 'bar' ]} component={renderFields}/>
</FormSection>
</FormSection>)
}
}
const TestForm = reduxForm({ form: 'testForm' })(Form)
const dom = TestUtils.renderIntoDocument(
<Provider store={store}>
<TestForm/>
</Provider>
)
const stub = TestUtils.findRenderedComponentWithType(dom, Fields)
expect(stub.names).toEqual([ 'foo.fighter.foo', 'foo.fighter.bar' ])
})

it('should prefix name when inside FormSection', () => {
const store = makeStore()
Expand Down
4 changes: 2 additions & 2 deletions src/__tests__/createFieldArrayProps.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import addExpectations from './addExpectations'

const describeCreateFieldProps = (name, structure, expect) => {
const { fromJS, getIn, size } = structure
const defaultParams = [ getIn, 'foo', () => 69 ]
const defaultParams = [ getIn, 'foo', undefined, () => 69 ]

describe(name, () => {
it('should pass props through', () => {
Expand Down Expand Up @@ -213,7 +213,7 @@ const describeCreateFieldProps = (name, structure, expect) => {
it('should provide get that uses passed in getValue', () => {
const value = fromJS([ 'a', 'b', 'c' ])
const getValue = index => value && getIn(value, index) + 'DOG'
const result = createFieldArrayProps(getIn, 'foo', getValue, { value })
const result = createFieldArrayProps(getIn, 'foo', undefined, getValue, { value })
expect(result.fields.get).toBeA('function')
expect(result.fields.get(0)).toBe('aDOG')
expect(result.fields.get(1)).toBe('bDOG')
Expand Down
9 changes: 5 additions & 4 deletions src/createFieldArrayProps.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const createFieldArrayProps = (getIn, name, getValue,
const createFieldArrayProps = (getIn, name, sectionPrefix, getValue,
{
arrayInsert, arrayMove, arrayPop, arrayPush, arrayRemove, arrayRemoveAll, arrayShift,
arraySplice, arraySwap, arrayUnshift, asyncError, // eslint-disable-line no-unused-vars
Expand All @@ -8,15 +8,16 @@ const createFieldArrayProps = (getIn, name, getValue,
}) => {
const error = syncError || asyncError || submitError
const warning = syncWarning
const fieldName = sectionPrefix ? name.replace(`${sectionPrefix}.`, '') : name
const finalProps = {
fields: {
_isFieldArray: true,
forEach: callback => (value || []).forEach((item, index) => callback(`${name}[${index}]`, index, finalProps.fields)),
forEach: callback => (value || []).forEach((item, index) => callback(`${fieldName}[${index}]`, index, finalProps.fields)),
get: getValue,
getAll: () => value,
insert: arrayInsert,
length,
map: callback => (value || []).map((item, index) => callback(`${name}[${index}]`, index, finalProps.fields)),
map: callback => (value || []).map((item, index) => callback(`${fieldName}[${index}]`, index, finalProps.fields)),
move: arrayMove,
name,
pop: () => {
Expand All @@ -25,7 +26,7 @@ const createFieldArrayProps = (getIn, name, getValue,
},
push: arrayPush,
reduce: (callback, initial) => (value || [])
.reduce((accumulator, item, index) => callback(accumulator, `${name}[${index}]`, index, finalProps.fields), initial),
.reduce((accumulator, item, index) => callback(accumulator, `${fieldName}[${index}]`, index, finalProps.fields), initial),
remove: arrayRemove,
removeAll: arrayRemoveAll,
shift: () => {
Expand Down
9 changes: 0 additions & 9 deletions src/util/__tests__/prefixName.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,4 @@ describe('prefixName', () => {
}
expect(prefixName(context, 'bar')).toBe('bar')
})

it('should not prefix array fields', () => {
const context = {
_reduxForm: {
sectionPrefix: 'foo'
}
}
expect(prefixName(context, 'bar.bar[0]')).toBe('bar.bar[0]')
})
})
Loading

0 comments on commit 162d45d

Please sign in to comment.