Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

executeAsync: Promised based execution GH-72 #163

Merged
merged 7 commits into from Jun 6, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
35 changes: 35 additions & 0 deletions README.md
Expand Up @@ -62,6 +62,8 @@ The component instance also has some utility functions that can be called. These
- `reset()` forces reset. See the [JavaScript API doc][js_api]
- `execute()` programmatically invoke the challenge
- need to call when using `"invisible"` reCAPTCHA - [example below](#invisible-recaptcha)
- `executeAsync()` programmatically invoke the challenge and return a promise that resolves to the token or errors(if encountered).
- alternative approach to `execute()` in combination with the `onChange()` prop - [example below](#invisible-recaptcha)

Example:
```javascript
Expand Down Expand Up @@ -109,6 +111,39 @@ ReactDOM.render(
);
```

Additionally, you can use the `executeAsync` method to use a promise based approach.

```jsx
import ReCAPTCHA from "react-google-recaptcha";


const ReCAPTCHAForm = (props) => {
const recaptchaRef = React.useRef();

const onSubmitWithReCAPTCHA = async () => {
const token = await recaptchaRef.current.executeAsync();

// apply to form data
}

return (
<form onSubmit={onSubmitWithReCAPTCHA}>
<ReCAPTCHA
ref={recaptchaRef}
size="invisible"
sitekey="Your client site key"
/>
</form>
)

}

ReactDOM.render(
<ReCAPTCHAForm />,
document.body
);
Comment on lines +116 to +144
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 Awesome example

```


### Advanced usage

Expand Down
26 changes: 24 additions & 2 deletions src/recaptcha.js
Expand Up @@ -34,6 +34,14 @@ export default class ReCAPTCHA extends React.Component {
}
}

executeAsync() {
return new Promise((resolve, reject) => {
this.executionResolve = resolve;
this.executionReject = reject;
this.execute();
});
}

reset() {
if (this.props.grecaptcha && this._widgetId !== undefined) {
this.props.grecaptcha.reset(this._widgetId);
Expand All @@ -49,11 +57,25 @@ export default class ReCAPTCHA extends React.Component {
}

handleErrored() {
if (this.props.onErrored) this.props.onErrored();
if (this.props.onErrored) {
this.props.onErrored();
}
if (this.executionReject) {
this.executionReject();
delete this.executionResolve;
delete this.executionReject;
}
}

handleChange(token) {
if (this.props.onChange) this.props.onChange(token);
if (this.props.onChange) {
this.props.onChange(token);
}
if (this.executionResolve) {
this.executionResolve(token);
delete this.executionReject;
delete this.executionResolve;
}
}

explicitRender() {
Expand Down
71 changes: 71 additions & 0 deletions test/recaptcha.spec.js
Expand Up @@ -88,6 +88,77 @@ describe("ReCAPTCHA", () => {
instance._internalRef.current.execute();
expect(grecaptchaMock.execute).toBeCalledWith(WIDGET_ID);
});
it("executeAsync, should call grecaptcha.execute with the widget id", () => {
const WIDGET_ID = "someWidgetId";
const grecaptchaMock = {
render() {
return WIDGET_ID;
},
execute: jest.fn(),
};
// wrapping component example that applies a ref to ReCAPTCHA
class WrappingComponent extends React.Component {
constructor(props) {
super(props);
this._internalRef = React.createRef();
}
render() {
return (
<div>
<ReCAPTCHA
sitekey="xxx"
size="invisible"
grecaptcha={grecaptchaMock}
onChange={jest.fn()}
ref={this._internalRef}
/>
</div>
);
}
}
const instance = ReactTestUtils.renderIntoDocument(React.createElement(WrappingComponent));
instance._internalRef.current.executeAsync();
expect(grecaptchaMock.execute).toBeCalledWith(WIDGET_ID);
});
it("executeAsync, should return a promise that resolves with the token", () => {
const WIDGET_ID = "someWidgetId";
const TOKEN = "someToken";
const grecaptchaMock = {
render(_, { callback }) {
this.callback = callback;
return WIDGET_ID;
},
execute() {
this.callback(TOKEN);
},
};
// wrapping component example that applies a ref to ReCAPTCHA
class WrappingComponent extends React.Component {
constructor(props) {
super(props);
this._internalRef = React.createRef();
}
render() {
return (
<div>
<ReCAPTCHA
sitekey="xxx"
size="invisible"
grecaptcha={grecaptchaMock}
onChange={jest.fn()}
ref={this._internalRef}
/>
</div>
);
}
}
const instance = ReactTestUtils.renderIntoDocument(React.createElement(WrappingComponent));
const executeAsyncDirectValue = instance._internalRef.current.executeAsync();
expect(executeAsyncDirectValue).toBeInstanceOf(Promise);
return executeAsyncDirectValue.then(executeAsyncResolveValue => {
expect(executeAsyncResolveValue).toBe(TOKEN);
});
});
describe("Expired", () => {
it("should call onChange with null when response is expired", () => {
const WIDGET_ID = "someWidgetId";
Expand Down