Skip to content

Commit a4f6ffe

Browse files
committed
only initialize api after fields have rendered
Hopefully will fix #21
1 parent 41052ae commit a4f6ffe

File tree

4 files changed

+49
-17
lines changed

4 files changed

+49
-17
lines changed

specs/__snapshots__/braintree.spec.jsx.snap

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,21 +144,27 @@ exports[`Braintree hosted fields renders and matches snapshot 1`] = `
144144
>
145145
<div
146146
className="braintree-hosted-field"
147+
id="braintree-field-wrapper-1"
147148
/>
148149
<div
149150
className="braintree-hosted-field"
151+
id="braintree-field-wrapper-2"
150152
/>
151153
<div
152154
className="braintree-hosted-field"
155+
id="braintree-field-wrapper-3"
153156
/>
154157
<div
155158
className="braintree-hosted-field"
159+
id="braintree-field-wrapper-4"
156160
/>
157161
<div
158162
className="braintree-hosted-field"
163+
id="braintree-field-wrapper-5"
159164
/>
160165
<div
161166
className="braintree-hosted-field"
167+
id="braintree-field-wrapper-6"
162168
/>
163169
</div>
164170
`;

specs/braintree.spec.jsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { Braintree, HostedField } from '../src/index.js';
77

88
jest.mock('braintree-web/client');
99
jest.mock('braintree-web/hosted-fields');
10+
jest.useFakeTimers();
1011

1112
let getToken;
1213

@@ -43,6 +44,7 @@ describe('Braintree hosted fields', () => {
4344

4445
it('registers when mounted', () => {
4546
mount(buildTree());
47+
jest.runAllTimers();
4648
expect(BraintreeClient.create).toHaveBeenCalledWith(expect.objectContaining({
4749
authorization: 'sandbox_g42y39zw_348pk9cgf3bgyw2b',
4850
}), expect.anything());
@@ -55,6 +57,7 @@ describe('Braintree hosted fields', () => {
5557
}));
5658
const onAuthorizationSuccess = jest.fn();
5759
mount(buildTree({ onAuthorizationSuccess }));
60+
jest.runAllTimers();
5861
expect(onAuthorizationSuccess.mock.calls.length).toEqual(1);
5962
});
6063

@@ -76,6 +79,7 @@ describe('Braintree hosted fields', () => {
7679
const clientInstance = jest.fn();
7780
BraintreeClient.create = jest.fn((args, cb) => cb(null, clientInstance));
7881
mount(buildTree({ authorization: 'onetwothree' }));
82+
jest.runAllTimers();
7983
expect(BraintreeClient.create).toHaveBeenCalledWith(
8084
{ authorization: 'onetwothree' }, expect.any(Function),
8185
);
@@ -85,6 +89,7 @@ describe('Braintree hosted fields', () => {
8589
it('can set token ref after render', () => {
8690
const styles = { foo: 'bar' };
8791
const fields = mount(buildTree({ styles, authorization: '' }));
92+
jest.runAllTimers();
8893
const clientInstance = jest.fn();
8994
BraintreeClient.create = jest.fn((args, cb) => cb(null, clientInstance));
9095
expect(BraintreeClient.create).not.toHaveBeenCalled();

src/api.js

Lines changed: 30 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@ function cap(string) {
88

99
export default class BraintreeClientApi {
1010

11-
fields = {};
11+
fields = Object.create(null);
1212

1313
_nextFieldId = 0;
1414

15-
fieldHandlers = {};
15+
fieldHandlers = Object.create(null);
1616

1717
constructor({
1818
authorization, styles, onAuthorizationSuccess, ...callbacks
@@ -26,6 +26,15 @@ export default class BraintreeClientApi {
2626
if (!authorization && this.authorization) {
2727
this.teardown();
2828
} else if (authorization && authorization !== this.authorization) {
29+
// fields have not yet checked in, delay setting so they can register
30+
if (0 === Object.keys(this.fields).length && !this.pendingAuthTimer) {
31+
this.pendingAuthTimer = setTimeout(() => {
32+
this.pendingAuthTimer = null;
33+
this.setAuthorization(authorization, onAuthorizationSuccess);
34+
}, 5);
35+
return;
36+
}
37+
2938
if (this.authorization) { this.teardown(); }
3039
this.authorization = authorization;
3140
Braintree.create({ authorization }, (err, clientInstance) => {
@@ -83,6 +92,10 @@ export default class BraintreeClientApi {
8392

8493
teardown() {
8594
if (this.hostedFields) { this.hostedFields.teardown(); }
95+
if (this.pendingAuthTimer) {
96+
clearTimeout(this.pendingAuthTimer);
97+
this.pendingAuthTimer = null;
98+
}
8699
}
87100

88101
checkInField({
@@ -97,20 +110,22 @@ export default class BraintreeClientApi {
97110
rejectUnsupportedCards,
98111
...handlers
99112
}) {
100-
this.fieldHandlers[type] = handlers;
101-
this.fields[type] = {
102-
formatInput,
103-
maxlength,
104-
minlength,
105-
placeholder,
106-
select,
107-
prefill,
108-
selector: `#${id}`,
113+
const onRenderComplete = () => {
114+
this.fieldHandlers[type] = handlers;
115+
this.fields[type] = {
116+
formatInput,
117+
maxlength,
118+
minlength,
119+
placeholder,
120+
select,
121+
prefill,
122+
selector: `#${id}`,
123+
};
124+
if (('number' === type) && rejectUnsupportedCards) {
125+
this.fields.number.rejectUnsupportedCards = true;
126+
}
109127
};
110-
if (('number' === type) && rejectUnsupportedCards) {
111-
this.fields.number.rejectUnsupportedCards = true;
112-
}
113-
return id;
128+
return [id, onRenderComplete];
114129
}
115130

116131
focusField(fieldType, cb) {

src/field.jsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ export default class BraintreeHostedField extends React.Component {
2424
braintreeApi: PropTypes.instanceOf(Api),
2525
}
2626

27+
state = {}
28+
2729
focus() {
2830
this.context.braintreeApi.focusField(this.props.type);
2931
}
@@ -37,7 +39,8 @@ export default class BraintreeHostedField extends React.Component {
3739
}
3840

3941
componentDidMount() {
40-
this.fieldId = this.context.braintreeApi.checkInField(this.props);
42+
const [ fieldId, onRenderComplete ] = this.context.braintreeApi.checkInField(this.props);
43+
this.setState({ fieldId }, onRenderComplete);
4144
}
4245

4346
get className() {
@@ -47,6 +50,9 @@ export default class BraintreeHostedField extends React.Component {
4750
}
4851

4952
render() {
50-
return <div id={this.fieldId} className={this.className} />;
53+
const { fieldId } = this.state;
54+
if (!fieldId) { return null; }
55+
56+
return <div id={fieldId} className={this.className} />;
5157
}
5258
}

0 commit comments

Comments
 (0)