Skip to content

Commit 1ec12aa

Browse files
authored
Merge pull request dozoisch#163 from stemsmit/GH-72_ExecuteAsync
executeAsync: Promised based execution dozoischGH-72
2 parents 942427d + b41e8dd commit 1ec12aa

File tree

3 files changed

+130
-2
lines changed

3 files changed

+130
-2
lines changed

README.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ The component instance also has some utility functions that can be called. These
6262
- `reset()` forces reset. See the [JavaScript API doc][js_api]
6363
- `execute()` programmatically invoke the challenge
6464
- need to call when using `"invisible"` reCAPTCHA - [example below](#invisible-recaptcha)
65+
- `executeAsync()` programmatically invoke the challenge and return a promise that resolves to the token or errors(if encountered).
66+
- alternative approach to `execute()` in combination with the `onChange()` prop - [example below](#invisible-recaptcha)
6567

6668
Example:
6769
```javascript
@@ -110,6 +112,39 @@ ReactDOM.render(
110112
);
111113
```
112114

115+
Additionally, you can use the `executeAsync` method to use a promise based approach.
116+
117+
```jsx
118+
import ReCAPTCHA from "react-google-recaptcha";
119+
120+
121+
const ReCAPTCHAForm = (props) => {
122+
const recaptchaRef = React.useRef();
123+
124+
const onSubmitWithReCAPTCHA = async () => {
125+
const token = await recaptchaRef.current.executeAsync();
126+
127+
// apply to form data
128+
}
129+
130+
return (
131+
<form onSubmit={onSubmitWithReCAPTCHA}>
132+
<ReCAPTCHA
133+
ref={recaptchaRef}
134+
size="invisible"
135+
sitekey="Your client site key"
136+
/>
137+
</form>
138+
)
139+
140+
}
141+
142+
ReactDOM.render(
143+
<ReCAPTCHAForm />,
144+
document.body
145+
);
146+
```
147+
113148

114149
### Advanced usage
115150

src/recaptcha.js

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,14 @@ export default class ReCAPTCHA extends React.Component {
3434
}
3535
}
3636

37+
executeAsync() {
38+
return new Promise((resolve, reject) => {
39+
this.executionResolve = resolve;
40+
this.executionReject = reject;
41+
this.execute();
42+
});
43+
}
44+
3745
reset() {
3846
if (this.props.grecaptcha && this._widgetId !== undefined) {
3947
this.props.grecaptcha.reset(this._widgetId);
@@ -49,11 +57,25 @@ export default class ReCAPTCHA extends React.Component {
4957
}
5058

5159
handleErrored() {
52-
if (this.props.onErrored) this.props.onErrored();
60+
if (this.props.onErrored) {
61+
this.props.onErrored();
62+
}
63+
if (this.executionReject) {
64+
this.executionReject();
65+
delete this.executionResolve;
66+
delete this.executionReject;
67+
}
5368
}
5469

5570
handleChange(token) {
56-
if (this.props.onChange) this.props.onChange(token);
71+
if (this.props.onChange) {
72+
this.props.onChange(token);
73+
}
74+
if (this.executionResolve) {
75+
this.executionResolve(token);
76+
delete this.executionReject;
77+
delete this.executionResolve;
78+
}
5779
}
5880

5981
explicitRender() {

test/recaptcha.spec.js

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,77 @@ describe("ReCAPTCHA", () => {
8888
instance._internalRef.current.execute();
8989
expect(grecaptchaMock.execute).toBeCalledWith(WIDGET_ID);
9090
});
91+
it("executeAsync, should call grecaptcha.execute with the widget id", () => {
92+
const WIDGET_ID = "someWidgetId";
93+
const grecaptchaMock = {
94+
render() {
95+
return WIDGET_ID;
96+
},
97+
execute: jest.fn(),
98+
};
99+
// wrapping component example that applies a ref to ReCAPTCHA
100+
class WrappingComponent extends React.Component {
101+
constructor(props) {
102+
super(props);
103+
this._internalRef = React.createRef();
104+
}
105+
render() {
106+
return (
107+
<div>
108+
<ReCAPTCHA
109+
sitekey="xxx"
110+
size="invisible"
111+
grecaptcha={grecaptchaMock}
112+
onChange={jest.fn()}
113+
ref={this._internalRef}
114+
/>
115+
</div>
116+
);
117+
}
118+
}
119+
const instance = ReactTestUtils.renderIntoDocument(React.createElement(WrappingComponent));
120+
instance._internalRef.current.executeAsync();
121+
expect(grecaptchaMock.execute).toBeCalledWith(WIDGET_ID);
122+
});
123+
it("executeAsync, should return a promise that resolves with the token", () => {
124+
const WIDGET_ID = "someWidgetId";
125+
const TOKEN = "someToken";
126+
const grecaptchaMock = {
127+
render(_, { callback }) {
128+
this.callback = callback;
129+
return WIDGET_ID;
130+
},
131+
execute() {
132+
this.callback(TOKEN);
133+
},
134+
};
135+
// wrapping component example that applies a ref to ReCAPTCHA
136+
class WrappingComponent extends React.Component {
137+
constructor(props) {
138+
super(props);
139+
this._internalRef = React.createRef();
140+
}
141+
render() {
142+
return (
143+
<div>
144+
<ReCAPTCHA
145+
sitekey="xxx"
146+
size="invisible"
147+
grecaptcha={grecaptchaMock}
148+
onChange={jest.fn()}
149+
ref={this._internalRef}
150+
/>
151+
</div>
152+
);
153+
}
154+
}
155+
const instance = ReactTestUtils.renderIntoDocument(React.createElement(WrappingComponent));
156+
const executeAsyncDirectValue = instance._internalRef.current.executeAsync();
157+
expect(executeAsyncDirectValue).toBeInstanceOf(Promise);
158+
return executeAsyncDirectValue.then(executeAsyncResolveValue => {
159+
expect(executeAsyncResolveValue).toBe(TOKEN);
160+
});
161+
});
91162
describe("Expired", () => {
92163
it("should call onChange with null when response is expired", () => {
93164
const WIDGET_ID = "someWidgetId";

0 commit comments

Comments
 (0)