Skip to content

Commit e3e6b0e

Browse files
committed
refactor with async-script-loader
- this simplifies the usage by a lot. It is no longer needed to add a script tag to the html
1 parent ce96914 commit e3e6b0e

File tree

12 files changed

+157
-73
lines changed

12 files changed

+157
-73
lines changed

README.md

Lines changed: 31 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,9 @@ npm install --save react-google-recaptcha
1515

1616
## Usage
1717

18-
First of all [sign up for an API key pair][signup]. Then add the Google reCAPTCHA script tag to your html.
18+
All you need to do is [sign up for an API key pair][signup]. You will need the client key.
1919

20-
The property set after `onload` is important and will be needed at render. This props is used to define a global callback, called by the Google script once it is loaded.
21-
22-
See the [Google reCAPTCHA docs][docs] for more info.
23-
24-
```html
25-
<script src="https://www.google.com/recaptcha/api.js?render=explicit&onload=onloadCallback" async defer></script>
26-
```
27-
28-
You can then use the reCAPTCHA
20+
You can then use the reCAPTCHA. The default require, imports a wrapped component that loads the reCAPTCHA script asynchronously.
2921

3022
```jsx
3123
var React = require("react");
@@ -37,9 +29,9 @@ function onChange(value) {
3729

3830
React.render(
3931
<ReCATPCHA
40-
sitekey="Your sitekey"
32+
refs="recaptcha"
33+
sitekey="Your client site key"
4134
onChange={onChange}
42-
onloadCallbackName="onloadcallback"
4335
/>, document.body);
4436
```
4537

@@ -51,20 +43,45 @@ Other properties can be used to customised the rendering.
5143
|:---- | ---- | ------ |
5244
| sitekey | string | The API client key |
5345
| onChange | func | The function to be called when the user completes successfully the captcha |
54-
| onloadCallbackName | string | The name the script will call onload. **This must be the same provided on script tag.**
5546
| theme | enum | *optional* `light` or `dark` The them of the widget *(__defaults:__ light)*
5647
| type | enum | *optional* `image` or `audio` The type of initial captcha *(__defaults:__ image)*
5748
| tabindex | number | *optional* The tabindex on the element *(__default:__ 0)*
58-
| onLoad | func | *optional* callback called when the widget has rendered
5949
| onExpired | func | *optional* callback when the challenge is expired and has to be redone by user. By default it will call the onChange with null to signify expired callback. |
6050

6151
## Component API
6252

53+
**To retrieve the component, when using the wrapper, do `this.refs.recaptcha.getComponent()`**
54+
6355
The component also has some utility functions that can be called.
6456

6557
- `getValue()` returns the value of the captcha field
6658
- `reset()` forces reset. See the [JavaScript API doc][js_api]
6759

60+
### Advanced usage
61+
62+
You can also use the barebone components doing the following. Using that component will oblige you to manage the grecaptcha dep and load the script by yourself.
63+
64+
```jsx
65+
var React = require("react");
66+
var ReCATPCHA = require("react-google-recaptcha/lib/recaptcha");
67+
68+
var grecaptchaObject = grecaptcha // You must provide access to the google grecaptcha object.
69+
70+
function onChange(value) {
71+
console.log("Captcha value:", value);
72+
}
73+
74+
React.render(
75+
<ReCATPCHA
76+
refs="recaptcha"
77+
sitekey="Your client site key"
78+
onChange={onChange}
79+
grecaptcha={grecaptchaObject}
80+
/>, document.body);
81+
```
82+
83+
84+
6885
## To Come Soon
6986
- tests
7087
- examples

karma.conf.js

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/* eslint no-var: 0 */
2-
require("babel/register");
2+
require("./register-babel");
33

44
var webpackConfig = require("./webpack/test.config.js");
55
var isCI = process.env.CONTINUOUS_INTEGRATION === "true";
@@ -19,19 +19,19 @@ module.exports = function (config) {
1919
],
2020

2121
preprocessors: {
22-
"test/index.js": ["webpack", "sourcemap"]
22+
"test/index.js": ["webpack", "sourcemap",],
2323
},
2424

2525
webpack: webpackConfig,
2626

2727
webpackMiddleware: {
28-
noInfo: isCI
28+
noInfo: isCI,
2929
},
3030

31-
reporters: ["mocha", "chai"],
31+
reporters: ["mocha"],
3232

3333
mochaReporter: {
34-
output: "autowatch"
34+
output: "autowatch",
3535
},
3636

3737
port: 9876,
@@ -47,6 +47,6 @@ module.exports = function (config) {
4747
captureTimeout: 60000,
4848
browserNoActivityTimeout: 30000,
4949

50-
singleRun: isCI
50+
singleRun: isCI,
5151
});
5252
};

package.json

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,15 @@
22
"name": "react-google-recaptcha",
33
"version": "0.1.0",
44
"description": "React Component Wrapper for Google reCAPTCHA",
5-
"main": "lib/recaptcha.js",
5+
"main": "lib/recaptcha-wrapper.js",
66
"directories": {
77
"lib": "lib/"
88
},
99
"scripts": {
1010
"build": "node run-babel tools/build.js",
1111
"lint": "eslint src test tools webpack karma.conf.js",
12-
"test": "karma start --single-run"
12+
"test": "karma start --single-run",
13+
"test-watch": "karma start"
1314
},
1415
"repository": {
1516
"type": "git",
@@ -32,15 +33,15 @@
3233
"react": ">=0.13"
3334
},
3435
"devDependencies": {
35-
"babel": "~5.0.10",
36-
"babel-core": "~5.0.10",
36+
"babel": "~5.0.12",
37+
"babel-core": "~5.0.12",
3738
"babel-eslint": "~2.0.2",
3839
"babel-loader": "~5.0.0",
39-
"chai": "^2.2.0",
40+
"chai": "~2.2.0",
4041
"child-process-promise": "~1.0.2",
4142
"colors": "~1.0.3",
42-
"es5-shim": "~4.1.0",
43-
"eslint": "~0.18.0",
43+
"es5-shim": "~4.1.1",
44+
"eslint": "~0.19.0",
4445
"eslint-plugin-react": "~2.1.0",
4546
"fs-promise": "~0.3.1",
4647
"karma": "~0.12.31",
@@ -53,9 +54,12 @@
5354
"karma-sourcemap-loader": "~0.3.4",
5455
"karma-webpack": "~1.5.0",
5556
"lodash": "~3.6.0",
56-
"mocha": "~2.2.3",
57+
"mocha": "~2.2.4",
5758
"react": "~0.13.1",
58-
"webpack": "~1.8.0",
59-
"yargs": "~3.7.0"
59+
"webpack": "~1.8.4",
60+
"yargs": "~3.7.1"
61+
},
62+
"dependencies": {
63+
"react-async-script": "~0.1.1"
6064
}
6165
}

register-babel.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
require("babel/register")({
2+
ignore: /node_modules/,
3+
optional: ["es7.objectRestSpread"],
4+
});

run-babel

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#!/usr/bin/env node
22
/* eslint no-var: 0 */
33
var path = require("path");
4-
require("babel/register");
4+
require("./register-babel");
55
var mod = require(path.join(__dirname, process.argv[2]));
66

77
if (typeof mod === "function") {

src/recaptcha-wrapper.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
"use strict";
2+
import React, { PropTypes } from "react";
3+
4+
import ReCAPTCHA from "./recaptcha";
5+
import makeAsyncScriptLoader from "react-async-script";
6+
7+
const callbackName = "onloadcallback";
8+
const URL = `https://www.google.com/recaptcha/api.js?onload=${callbackName}&render=explicit`;
9+
const globalName = "grecaptcha";
10+
11+
export default makeAsyncScriptLoader(ReCAPTCHA, URL, {
12+
callbackName: callbackName,
13+
globalName:globalName,
14+
});

src/recaptcha.js

Lines changed: 17 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,19 @@
11
/*global grecaptcha*/
22
"use strict";
3-
import React from "react";
4-
const PropTypes = React.PropTypes;
3+
import React, { PropTypes } from "react";
54

65
const ReCAPTCHA = React.createClass({
76
displayName: "react-reCAPTCHA",
87
propTypes: {
98
sitekey: PropTypes.string.isRequired,
109
onChange: PropTypes.func.isRequired,
11-
onloadCallbackName: PropTypes.string.isRequired, // Name on the script tag onload query parameter
1210
theme: PropTypes.oneOf(["dark", "light"]),
1311
type: PropTypes.oneOf(["image", "audio"]),
14-
elementId: PropTypes.string,
1512
tabindex: PropTypes.number,
16-
onLoad: PropTypes.func,
1713
onExpired: PropTypes.func,
1814
},
1915

20-
getDefaultState() {
16+
getInitialState() {
2117
return {};
2218
},
2319

@@ -26,20 +22,19 @@ const ReCAPTCHA = React.createClass({
2622
theme: "light",
2723
type: "image",
2824
tabindex: 0,
29-
elementId: "react-reCAPTCHA",
3025
};
3126
},
3227

3328
getValue() {
34-
if (typeof grecaptcha !== "undefined" && this.state.widgetId) {
35-
return grecaptcha.getResponse(this.state.widgetId);
29+
if (this.props.grecaptcha && this.state.widgetId) {
30+
return this.props.grecaptcha.getResponse(this.state.widgetId);
3631
}
3732
return null;
3833
},
3934

4035
reset() {
41-
if (typeof grecaptcha !== "undefined" && this.state.widgetId) {
42-
grecaptcha.reset(this.state.widgetId);
36+
if (this.props.grecaptcha && this.state.widgetId) {
37+
this.props.grecaptcha.reset(this.state.widgetId);
4338
}
4439
},
4540

@@ -51,9 +46,10 @@ const ReCAPTCHA = React.createClass({
5146
}
5247
},
5348

54-
explicitRender() {
55-
if (typeof grecaptcha !== "undefined") {
56-
let id = grecaptcha.render(this.refs.captcha.getDOMNode(), {
49+
explicitRender(cb) {
50+
if (this.props.grecaptcha && !this.state.widgetId) {
51+
this.refs.captcha.getDOMNode();
52+
let id = this.props.grecaptcha.render(this.refs.captcha.getDOMNode(), {
5753
sitekey: this.props.sitekey,
5854
callback: this.props.onChange,
5955
theme: this.props.theme,
@@ -63,29 +59,23 @@ const ReCAPTCHA = React.createClass({
6359
});
6460
this.setState({
6561
widgetId: id,
66-
});
62+
}, cb);
6763
}
6864
},
6965

70-
handleLoad() {
66+
componentDidMount() {
7167
this.explicitRender();
72-
if (this.props.onLoad) {
73-
this.props.onLoad();
74-
}
7568
},
7669

77-
componentDidMount() {
78-
// If script is not loaded, set the callback on window.
79-
if (typeof grecaptcha === "undefined") {
80-
window[this.props.onloadCallbackName] = this.handleLoad;
81-
} else {
82-
this.handleLoad();
83-
}
70+
componentDidUpdate() {
71+
this.explicitRender();
8472
},
8573

8674
render() {
75+
// consume properties owned by the reCATPCHA, pass the rest to the div so the user can style it.
76+
let { sitekey, onChange, theme, type, tabindex, onExpired, ...childProps } = this.props;
8777
return (
88-
<div ref="captcha" id={this.props.elementId} />
78+
<div {...childProps} ref="captcha" />
8979
);
9080
}
9181
});

test/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
import "es5-shim";
2-
const testsContext = require.context(".", true, /Spec$/);
2+
const testsContext = require.context(".", true, /-spec$/);
33
testsContext.keys().forEach(testsContext);

test/recaptcha-spec.js

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import React from "react";
2+
import ReactTestUtils from "react/lib/ReactTestUtils";
3+
import ReCAPTCHA from "../src/recaptcha";
4+
5+
describe("ReCAPTCHA", function () {
6+
let callbackName = "onloadcallback";
7+
8+
afterEach(function () {
9+
delete window.grecaptcha;
10+
delete window.callbackName;
11+
});
12+
13+
it("Rendered Component should be a div", function () {
14+
let instance = ReactTestUtils.renderIntoDocument(
15+
<ReCAPTCHA siteKey="xxx" onloadCallbackName={callbackName} />
16+
);
17+
assert.equal(instance.getDOMNode().nodeName, "DIV");
18+
});
19+
it("Rendered Component should contained passed props", function () {
20+
let props = {
21+
className: "TheClassName",
22+
id: "superdefinedId",
23+
};
24+
let className = "TheClassName";
25+
let instance = ReactTestUtils.renderIntoDocument(
26+
<ReCAPTCHA siteKey="xxx" {...props} />
27+
);
28+
assert.equal(instance.getDOMNode().id, props.id);
29+
assert.match(instance.getDOMNode().className, new RegExp(props.className));
30+
});
31+
32+
it("should register a callback on the window when grecaptcha is not loaded", function () {
33+
let instance = ReactTestUtils.renderIntoDocument(
34+
<ReCAPTCHA siteKey="xxx" />
35+
);
36+
assert.isNotNull(window[callbackName]);
37+
});
38+
it("should call grecaptcha.render, when it is already loaded", function (done) {
39+
let grecaptchaMock = {
40+
render() {
41+
done();
42+
},
43+
};
44+
let instance = ReactTestUtils.renderIntoDocument(
45+
<ReCAPTCHA siteKey="xxx" grecaptcha={grecaptchaMock} />
46+
);
47+
});
48+
it("reset, should call grecaptcha.reset with the widget id", function (done) {
49+
let grecaptchaMock = {
50+
render() {
51+
return "someWidgetId";
52+
},
53+
54+
reset(widgetId) {
55+
assert.isNotNull(widgetId);
56+
done();
57+
},
58+
};
59+
let instance = ReactTestUtils.renderIntoDocument(
60+
<ReCAPTCHA siteKey="xxx" grecaptcha={grecaptchaMock} />
61+
);
62+
instance.reset();
63+
});
64+
describe("Expired", function () {
65+
it("should call onChange with null when response is expired");
66+
it("should call onExpired when response is expired");
67+
});
68+
});

test/recaptchaSpec.js

Lines changed: 0 additions & 12 deletions
This file was deleted.

0 commit comments

Comments
 (0)