Skip to content

Commit 0596d33

Browse files
committed
fix: Rerender after language or theme changed
1 parent 4e40121 commit 0596d33

File tree

5 files changed

+103
-49
lines changed

5 files changed

+103
-49
lines changed

README.md

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -41,15 +41,15 @@ Usually, your application only needs one provider. You should place it as high a
4141

4242
Same thing applied when you use this library with framework such as Next.js or React Router and only want to include the script on a single page. Try to make sure you only have one instance of the provider on a React tree and to place it as high (on the tree) as possible.
4343

44-
| **Props** | **Type** | **Default** | **Required?** | **Note** |
45-
| --------------- | :------: | ----------: | ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
46-
| reCaptchaKey | String | | Yes | Your recaptcha key, get one from [here](https://www.google.com/recaptcha/intro/v3.html) |
47-
| scriptProps | Object | | No | You can customize the injected `script` tag with this prop. It allows you to add `async`, `defer`, `nonce` attributes to the script tag. You can also control whether the injected script will be added to the document body or head with `appendTo` attribute. |
48-
| language | String | | No | optional prop to support different languages that is supported by Google Recaptcha. https://developers.google.com/recaptcha/docs/language |
49-
| useRecaptchaNet | Boolean | false | No | The provider also provide the prop `useRecaptchaNet` to load script from `recaptcha.net`: https://developers.google.com/recaptcha/docs/faq#can-i-use-recaptcha-globally |
50-
| useEnterprise | Boolean | false | No | [Enterprise option](#enterprise) |
51-
| inlineBadgeId | String | | No | Container ID where the recaptcha badge will be rendered |
52-
| parameters | Object | | No | Configuration for the inline badge (See google recaptcha docs) |
44+
| **Props** | **Type** | **Default** | **Required?** | **Note** |
45+
|----------------------|:----------------:| ----------: | ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
46+
| reCaptchaKey | String | | Yes | Your recaptcha key, get one from [here](https://www.google.com/recaptcha/intro/v3.html) |
47+
| scriptProps | Object | | No | You can customize the injected `script` tag with this prop. It allows you to add `async`, `defer`, `nonce` attributes to the script tag. You can also control whether the injected script will be added to the document body or head with `appendTo` attribute. |
48+
| language | String | | No | optional prop to support different languages that is supported by Google Recaptcha. https://developers.google.com/recaptcha/docs/language |
49+
| useRecaptchaNet | Boolean | false | No | The provider also provide the prop `useRecaptchaNet` to load script from `recaptcha.net`: https://developers.google.com/recaptcha/docs/faq#can-i-use-recaptcha-globally |
50+
| useEnterprise | Boolean | false | No | [Enterprise option](#enterprise) |
51+
| container.element | String HTMLElement | | No | Container ID where the recaptcha badge will be rendered |
52+
| container.parameters | Object | | No | Configuration for the inline badge (See google recaptcha docs) |
5353

5454
```javascript
5555
import { GoogleReCaptchaProvider } from 'react-google-recaptcha-v3';
@@ -66,10 +66,12 @@ ReactDom.render(
6666
appendTo: 'head', // optional, default to "head", can be "head" or "body",
6767
nonce: undefined // optional, default undefined
6868
}}
69-
inlineBadgeId="g-recaptcha" // optional to make it inline (there must exists a container with this Id)
70-
parameters={{// optional inline badge configuration
71-
badge: 'inline',
72-
size: 'invisible'
69+
container={{ // optional to render inside custom element
70+
element: "[required_id_or_htmlelement]",
71+
parameters: {
72+
badge: '[inline|bottomright|bottomleft]', // optional, default undefined
73+
theme: 'dark', // optional, default undefined
74+
}
7375
}}
7476
>
7577
<YourApp />

example/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { WithGoogleRecaptchaExample } from './with-google-recaptcha-example';
77
ReactDom.render(
88
<GoogleReCaptchaProvider
99
useRecaptchaNet
10-
reCaptchaKey={process.env.RECAPTCHA_KEY}
10+
reCaptchaKey={process.env.RECAPTCHA_KEY as string}
1111
scriptProps={{ async: true, defer: true, appendTo: 'body' }}
1212
>
1313
<h2>Google Recaptcha Example</h2>

src/google-recaptcha-provider.tsx

Lines changed: 27 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import React, {
2-
useRef,
3-
useMemo,
4-
useState,
5-
useEffect,
6-
useCallback,
72
createContext,
8-
ReactNode
3+
ReactNode,
4+
useCallback,
5+
useEffect,
6+
useMemo,
7+
useRef,
8+
useState
99
} from 'react';
1010
import {
1111
cleanGoogleRecaptcha,
@@ -30,23 +30,23 @@ interface IGoogleReCaptchaProviderProps {
3030
id?: string;
3131
onLoadCallbackName?: string;
3232
};
33-
inlineBadgeId?: string | HTMLElement;
34-
parameters?: {
35-
sitekey?: string;
36-
badge?: string;
37-
theme?: string;
38-
size?: string;
39-
tabindex?: number;
40-
callback?: () => void;
41-
expiredCallback?: () => void;
42-
errorCallback?: () => void;
33+
container?: {
34+
element: string | HTMLElement;
35+
parameters: {
36+
badge?: 'inline' | 'bottomleft' | 'bottomright';
37+
theme?: 'dark' | 'light';
38+
tabindex?: number;
39+
callback?: () => void;
40+
expiredCallback?: () => void;
41+
errorCallback?: () => void;
42+
}
4343
};
4444
children: ReactNode;
4545
}
4646

4747
export interface IGoogleReCaptchaConsumerProps {
4848
executeRecaptcha?: (action?: string) => Promise<string>;
49-
inlineBadgeId?: string | HTMLElement;
49+
container?: string | HTMLElement;
5050
}
5151

5252
const GoogleReCaptchaContext = createContext<IGoogleReCaptchaConsumerProps>({
@@ -66,8 +66,7 @@ export function GoogleReCaptchaProvider({
6666
useRecaptchaNet = false,
6767
scriptProps,
6868
language,
69-
inlineBadgeId,
70-
parameters,
69+
container,
7170
children
7271
}: IGoogleReCaptchaProviderProps) {
7372
const [greCaptchaInstance, setGreCaptchaInstance] = useState<null | {
@@ -76,7 +75,7 @@ export function GoogleReCaptchaProvider({
7675
const clientId = useRef<number | string>(reCaptchaKey);
7776

7877
const scriptPropsJson = JSON.stringify(scriptProps);
79-
const parametersJson = JSON.stringify(parameters);
78+
const parametersJson = JSON.stringify(container?.parameters);
8079

8180
useEffect(() => {
8281
if (!reCaptchaKey) {
@@ -100,9 +99,9 @@ export function GoogleReCaptchaProvider({
10099
badge: 'inline',
101100
size: 'invisible',
102101
sitekey: reCaptchaKey,
103-
...(parameters || {})
102+
...(container?.parameters || {})
104103
};
105-
clientId.current = grecaptcha.render(inlineBadgeId, params);
104+
clientId.current = grecaptcha.render(container?.element, params);
106105
};
107106

108107
const onLoad = () => {
@@ -128,7 +127,7 @@ export function GoogleReCaptchaProvider({
128127
};
129128

130129
injectGoogleReCaptchaScript({
131-
render: inlineBadgeId ? 'explicit' : reCaptchaKey,
130+
render: container?.element ? 'explicit' : reCaptchaKey,
132131
onLoadCallbackName,
133132
useEnterprise,
134133
useRecaptchaNet,
@@ -139,15 +138,16 @@ export function GoogleReCaptchaProvider({
139138
});
140139

141140
return () => {
142-
cleanGoogleRecaptcha(scriptId);
141+
cleanGoogleRecaptcha(scriptId, container?.element);
143142
};
144143
}, [
145144
useEnterprise,
146145
useRecaptchaNet,
147146
scriptPropsJson,
148147
parametersJson,
149148
language,
150-
reCaptchaKey
149+
reCaptchaKey,
150+
container?.element,
151151
]);
152152

153153
const executeRecaptcha = useCallback(
@@ -166,9 +166,9 @@ export function GoogleReCaptchaProvider({
166166
const googleReCaptchaContextValue = useMemo(
167167
() => ({
168168
executeRecaptcha: greCaptchaInstance ? executeRecaptcha : undefined,
169-
inlineBadgeId,
169+
container: container?.element,
170170
}),
171-
[executeRecaptcha, greCaptchaInstance, inlineBadgeId]
171+
[executeRecaptcha, greCaptchaInstance, container?.element]
172172
);
173173

174174
return (

src/google-recaptcha.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,10 @@ export function GoogleReCaptcha({
3737
handleExecuteRecaptcha();
3838
}, [action, onVerify, refreshReCaptcha, googleRecaptchaContextValue]);
3939

40-
const { inlineBadgeId } = googleRecaptchaContextValue;
41-
42-
if (typeof inlineBadgeId === 'string') {
43-
return <div id={inlineBadgeId} />;
40+
const { container } = googleRecaptchaContextValue;
41+
42+
if (typeof container === 'string') {
43+
return <div id={container} />;
4444
}
4545

4646
return null;

src/utils.ts

Lines changed: 56 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,16 +57,68 @@ export const isScriptInjected = (scriptId: string) =>
5757
!!document.querySelector(`#${scriptId}`);
5858

5959
/**
60-
* Function to clean google recaptcha script
60+
* Function to remove default badge
6161
*
62-
* @param scriptId
62+
* @returns
6363
*/
64-
export const cleanGoogleRecaptcha = (scriptId: string) => {
65-
// remove badge
64+
const removeDefaultBadge = () => {
6665
const nodeBadge = document.querySelector('.grecaptcha-badge');
6766
if (nodeBadge && nodeBadge.parentNode) {
6867
document.body.removeChild(nodeBadge.parentNode);
6968
}
69+
};
70+
71+
/**
72+
* Function to clear custom badge
73+
*
74+
* @returns
75+
*/
76+
const cleanCustomBadge = (customBadge: HTMLElement | null) => {
77+
if (!customBadge) {
78+
return;
79+
}
80+
81+
while (customBadge.lastChild) {
82+
customBadge.lastChild.remove();
83+
}
84+
};
85+
86+
/**
87+
* Function to clean node of badge element
88+
*
89+
* @param container
90+
* @returns
91+
*/
92+
export const cleanBadge = (container?: HTMLElement | string) => {
93+
if (!container) {
94+
removeDefaultBadge();
95+
96+
return;
97+
}
98+
99+
if (typeof container === 'string') {
100+
const customBadge = document.getElementById(container);
101+
102+
cleanCustomBadge(customBadge);
103+
104+
return;
105+
}
106+
107+
cleanCustomBadge(container);
108+
};
109+
110+
/**
111+
* Function to clean google recaptcha script
112+
*
113+
* @param scriptId
114+
* @param container
115+
*/
116+
export const cleanGoogleRecaptcha = (scriptId: string, container?: HTMLElement | string) => {
117+
// remove badge
118+
cleanBadge(container);
119+
120+
// remove old config from window
121+
(window as any).___grecaptcha_cfg = undefined;
70122

71123
// remove script
72124
const script = document.querySelector(`#${scriptId}`);

0 commit comments

Comments
 (0)