Skip to content

Commit 52ae505

Browse files
committed
Rewrite with hooks.
1 parent ee494be commit 52ae505

File tree

5 files changed

+471
-414
lines changed

5 files changed

+471
-414
lines changed

README.md

Lines changed: 30 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
# react-google-invisible-recaptcha #
22

3-
A React component which is simply interested in Google invisible reCAPTCHA.
3+
A React component which is interested in only Google invisible reCAPTCHA.
44

55
* Support multiple reCAPTCHA widgets on one page.
6-
* Vanilla JS.
6+
* Support React hooks.
77

88
## [Demo][demo] ##
99

@@ -13,44 +13,7 @@ When reCAPTCHA is resolved, the demo page shows the result token for demo purpos
1313

1414
## Example ##
1515

16-
Below is a component which coordinates the procedure.
17-
18-
```js
19-
class Example extends React.Component {
20-
constructor( props ) {
21-
super( props );
22-
this.state = { value: '' };
23-
this.onSubmit = this.onSubmit.bind( this );
24-
this.onResolved = this.onResolved.bind( this );
25-
}
26-
render() {
27-
return (
28-
<div>
29-
<input
30-
type="text"
31-
value={ this.state.value }
32-
onChange={ event => this.setState( { value: event.target.value } ) } />
33-
<button onClick={ this.onSubmit }>Submit</button>
34-
<Recaptcha
35-
ref={ ref => this.recaptcha = ref }
36-
sitekey="<sitekey>"
37-
onResolved={ this.onResolved } />
38-
</div>
39-
);
40-
}
41-
onSubmit() {
42-
if ( '' == this.state.value ) {
43-
alert( 'Validation failed! Input cannot be empty.' );
44-
this.recaptcha.reset();
45-
} else {
46-
this.recaptcha.execute();
47-
}
48-
}
49-
onResolved() {
50-
alert( 'Recaptcha resolved with response: ' + this.recaptcha.getResponse() );
51-
}
52-
}
53-
```
16+
See the `example/` folder for an example.
5417

5518
## Install ##
5619

@@ -64,9 +27,9 @@ npm install react-google-invisible-recaptcha --save
6427
import Recaptcha from 'react-google-invisible-recaptcha';
6528

6629
<Recaptcha
67-
ref={ ref => this.recaptcha = ref }
68-
sitekey={ <sitekey> }
69-
onResolved={ () => console.log( 'Human detected.' ) } />
30+
ref={refRecaptcha}
31+
sitekey={<sitekey>}
32+
onResolved={() => console.log('Human detected.')} />
7033
```
7134

7235
## Configuration ##
@@ -80,22 +43,40 @@ A few optional props you can tweak.
8043
* badge: `bottomright`, `bottomleft`, or `inline`. **Default: bottomright.**
8144
* locale: in which language it speaks. **Default: en.**
8245
* nonce: nonce included in the reCAPTCHA script tag. **Default: undefined.**
83-
* onResolved: callback when the recaptcha is resolved. **Default: noop.**
84-
* onError: callback when the recaptcha encounters an error. **Default: noop.**
8546
* onExpired: callback when the recaptcha response expires. **Default: noop.**
47+
* onError: callback when the recaptcha encounters an error. **Default: noop.**
8648
* onLoaded: callback when the recaptcha is loaded. **Default: noop.**
49+
* onResolved: callback when the recaptcha is resolved. **Default: noop.**
8750
* style: custom CSS applied to the root node. **Default: undefined.**
8851
* tabindex: tabindex of the challenge. **Default: 0.**
8952

9053
## APIs ##
9154

9255
```js
93-
<Recaptcha ref={ ref => this.recaptcha = ref } ... />
56+
// Functional component with React hooks.
57+
const refRecaptcha = React.useRef(null);
58+
<Recaptcha ref={refRecaptcha} ... />
59+
60+
// Class component.
61+
this.refRecaptcha = React.createRef();
62+
<Recaptcha ref={refRecaptcha} ... />
63+
64+
// refRecaptcha.current.callbacks.execute function which invokes the reCAPTCHA check.
65+
// refRecaptcha.current.callbacks.reset function which resets the reCAPTCHA widget.
66+
// refRecaptcha.current.callbacks.getResponse function which returns the response token.
9467
```
9568

96-
* _this.recaptcha.execute_ function which invokes the reCAPTCHA check.
97-
* _this.recaptcha.reset_ function which resets the reCAPTCHA widget.
98-
* _this.recaptcha.getResponse_ function which returns the response token.
69+
## Migration from 0.x to 1.0.0
70+
71+
```js
72+
// version 0.x
73+
<Recaptcha ref={ref => this.recaptcha = ref} ... />
74+
// this.recaptcha.execute invokes the reCAPTCHA check.
75+
76+
// version 1.0.0
77+
<Recaptcha ref={refRecaptcha} ... />
78+
// refRecaptcha.current.callbacks.execute invokes the reCAPTCHA check.
79+
```
9980

10081
## License ##
10182

dist/index.js

Lines changed: 90 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -6,28 +6,20 @@ Object.defineProperty(exports, "__esModule", {
66

77
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
88

9-
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
9+
var _propTypes = require('prop-types');
10+
11+
var _propTypes2 = _interopRequireDefault(_propTypes);
1012

1113
var _react = require('react');
1214

1315
var _react2 = _interopRequireDefault(_react);
1416

15-
var _propTypes = require('prop-types');
16-
17-
var _propTypes2 = _interopRequireDefault(_propTypes);
18-
1917
var _v = require('uuid/v4');
2018

2119
var _v2 = _interopRequireDefault(_v);
2220

2321
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
2422

25-
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
26-
27-
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
28-
29-
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
30-
3123
var renderers = [];
3224

3325
var injectScript = function injectScript(locale, nonce) {
@@ -51,102 +43,101 @@ var injectScript = function injectScript(locale, nonce) {
5143
document.body.appendChild(script);
5244
};
5345

54-
var GoogleRecaptcha = function (_React$Component) {
55-
_inherits(GoogleRecaptcha, _React$Component);
56-
57-
function GoogleRecaptcha() {
58-
_classCallCheck(this, GoogleRecaptcha);
59-
60-
return _possibleConstructorReturn(this, (GoogleRecaptcha.__proto__ || Object.getPrototypeOf(GoogleRecaptcha)).apply(this, arguments));
61-
}
62-
63-
_createClass(GoogleRecaptcha, [{
64-
key: 'componentDidMount',
65-
value: function componentDidMount() {
66-
var _this2 = this;
67-
68-
var _props = this.props,
69-
badge = _props.badge,
70-
locale = _props.locale,
71-
nonce = _props.nonce,
72-
onExpired = _props.onExpired,
73-
onError = _props.onError,
74-
onLoaded = _props.onLoaded,
75-
onResolved = _props.onResolved,
76-
sitekey = _props.sitekey,
77-
tabindex = _props.tabindex;
78-
79-
80-
this.callbackName = 'GoogleRecaptchaResolved-' + (0, _v2.default)();
81-
window[this.callbackName] = onResolved;
82-
83-
var loaded = function loaded() {
84-
if (_this2.container) {
85-
var wrapper = document.createElement('div');
86-
// This wrapper must be appended to the DOM immediately before rendering
87-
// reCaptcha. Otherwise multiple reCaptchas will act jointly somehow.
88-
_this2.container.appendChild(wrapper);
89-
var recaptchaId = window.grecaptcha.render(wrapper, {
90-
badge: badge,
91-
callback: _this2.callbackName,
92-
'error-callback': onError,
93-
'expired-callback': onExpired,
94-
sitekey: sitekey,
95-
size: 'invisible',
96-
tabindex: tabindex
97-
});
98-
_this2.execute = function () {
46+
var defaultProps = {
47+
badge: 'bottomright',
48+
locale: '',
49+
onExpired: function onExpired() {},
50+
onError: function onError() {},
51+
onLoaded: function onLoaded() {},
52+
onResolved: function onResolved() {},
53+
tabindex: 0
54+
};
55+
56+
function GoogleRecaptcha(props, refContainer) {
57+
var _props$badge = props.badge,
58+
badge = _props$badge === undefined ? defaultProps.badge : _props$badge,
59+
_props$locale = props.locale,
60+
locale = _props$locale === undefined ? defaultProps.locale : _props$locale,
61+
nonce = props.nonce,
62+
_props$onExpired = props.onExpired,
63+
onExpired = _props$onExpired === undefined ? defaultProps.onExpired : _props$onExpired,
64+
_props$onError = props.onError,
65+
onError = _props$onError === undefined ? defaultProps.onError : _props$onError,
66+
_props$onLoaded = props.onLoaded,
67+
onLoaded = _props$onLoaded === undefined ? defaultProps.onLoaded : _props$onLoaded,
68+
_props$onResolved = props.onResolved,
69+
onResolved = _props$onResolved === undefined ? defaultProps.onResolved : _props$onResolved,
70+
sitekey = props.sitekey,
71+
style = props.style,
72+
_props$tabindex = props.tabindex,
73+
tabindex = _props$tabindex === undefined ? defaultProps.tabindex : _props$tabindex;
74+
75+
76+
var callbackName = 'GoogleRecaptchaResolved-' + (0, _v2.default)();
77+
78+
_react2.default.useEffect(function () {
79+
window[callbackName] = onResolved;
80+
81+
var domNode = refContainer.current;
82+
var callbacks = {};
83+
84+
var loaded = function loaded() {
85+
if (refContainer.current) {
86+
var wrapper = document.createElement('div');
87+
// This wrapper must be appended to the DOM immediately before rendering
88+
// reCaptcha. Otherwise multiple reCaptchas will act jointly somehow.
89+
refContainer.current.appendChild(wrapper);
90+
var recaptchaId = window.grecaptcha.render(wrapper, {
91+
badge: badge,
92+
callback: callbackName,
93+
'error-callback': onError,
94+
'expired-callback': onExpired,
95+
sitekey: sitekey,
96+
size: 'invisible',
97+
tabindex: tabindex
98+
});
99+
refContainer.current.callbacks = {
100+
execute: function execute() {
99101
return window.grecaptcha.execute(recaptchaId);
100-
};
101-
_this2.reset = function () {
102+
},
103+
reset: function reset() {
102104
return window.grecaptcha.reset(recaptchaId);
103-
};
104-
_this2.getResponse = function () {
105+
},
106+
getResponse: function getResponse() {
105107
return window.grecaptcha.getResponse(recaptchaId);
106-
};
107-
onLoaded();
108-
}
109-
};
110-
111-
if (window.grecaptcha && window.grecaptcha.render && window.grecaptcha.execute && window.grecaptcha.reset && window.grecaptcha.getResponse) {
112-
loaded();
113-
} else {
114-
renderers.push(loaded);
115-
if (!document.querySelector('#recaptcha')) {
116-
injectScript(locale, nonce);
117-
}
108+
}
109+
};
110+
callbacks = _extends({}, refContainer.current.callbacks);
111+
onLoaded();
112+
}
113+
};
114+
115+
if (window.grecaptcha && window.grecaptcha.render && window.grecaptcha.execute && window.grecaptcha.reset && window.grecaptcha.getResponse) {
116+
loaded();
117+
} else {
118+
renderers.push(loaded);
119+
if (!document.querySelector('#recaptcha')) {
120+
injectScript(locale, nonce);
118121
}
119122
}
120-
}, {
121-
key: 'componentWillUnmount',
122-
value: function componentWillUnmount() {
123-
while (this.container.firstChild) {
124-
this.container.removeChild(this.container.firstChild);
123+
124+
return function () {
125+
while (domNode.firstChild) {
126+
domNode.removeChild(domNode.firstChild);
125127
}
128+
126129
// There is a chance that the reCAPTCHA API lib is not loaded yet, so check
127130
// before invoking reset.
128-
if (this.reset) {
129-
this.reset();
131+
if (callbacks.reset) {
132+
callbacks.reset();
130133
}
131-
delete window[this.callbackName];
132-
}
133-
}, {
134-
key: 'render',
135-
value: function render() {
136-
var _this3 = this;
137-
138-
var style = this.props.style;
139-
140-
return _react2.default.createElement('div', _extends({
141-
ref: function ref(_ref) {
142-
return _this3.container = _ref;
143-
}
144-
}, style && { style: style }));
145-
}
146-
}]);
147134

148-
return GoogleRecaptcha;
149-
}(_react2.default.Component);
135+
delete window[callbackName];
136+
};
137+
}, []);
138+
139+
return _react2.default.createElement('div', _extends({ ref: refContainer }, style && { style: style }));
140+
}
150141

151142
GoogleRecaptcha.propTypes = {
152143
badge: _propTypes2.default.oneOf(['bottomright', 'bottomleft', 'inline']),
@@ -161,14 +152,6 @@ GoogleRecaptcha.propTypes = {
161152
tabindex: _propTypes2.default.number
162153
};
163154

164-
GoogleRecaptcha.defaultProps = {
165-
badge: 'bottomright',
166-
locale: '',
167-
onExpired: function onExpired() {},
168-
onError: function onError() {},
169-
onLoaded: function onLoaded() {},
170-
onResolved: function onResolved() {},
171-
tabindex: 0
172-
};
155+
GoogleRecaptcha.defaultProps = defaultProps;
173156

174-
exports.default = GoogleRecaptcha;
157+
exports.default = _react2.default.forwardRef(GoogleRecaptcha);

0 commit comments

Comments
 (0)