marsidev / react-turnstile

Cloudflare Turnstile integration for React.
https://www.npmjs.com/package/@marsidev/react-turnstile
MIT License
414 stars 25 forks source link

Multiple Turnstile components on a page not working #16

Closed aprilnickelgc closed 1 year ago

aprilnickelgc commented 1 year ago

Do you want to request a feature or report a bug?

Bug

What is the current behavior?

Hello! I'd like to render multiple forms on one page, each with its own Turnstile widget. When I do this, only the last one renders the Turnstile widget.

Note: If you make a change to the first component (for example, remove the cData option, line 18, in the CodeSandbox linked below), the hot reload makes the widget render & get the token. Now, if you make a change to the second component (remove the cData option, line 31 now), you'll see the second widget show up - and now both widgets, with tokens, are on the page, which looks like it's fixed. However, if you refresh the browser pane, only the second Turnstile widget shows up - same as when the CodeSandbox originally loaded.

Screenshot of just one widget working on initial page load:

image

If the current behavior is a bug, please provide the steps to reproduce and if possible a minimal demo of the problem. Your bug will get fixed much faster if we can run your code and it doesn't have dependencies other than React. Paste the link to your CodeSandbox example below:

https://codesandbox.io/s/distracted-resonance-jy1lg2?file=/src/App.tsx

What is the expected behavior?

Each form's Turnstile component should render the widget & get the token returned back.

Which versions of React, and which browser / OS are affected by this issue? Did this work in previous versions of React?

marsidev commented 1 year ago

Hey @aprilnickelgc , thanks for submitting this issue. I agree this should not happen. I'll describe why this happened:

The library automatically injects the Cloudflare script and assigns an onload function where we actually render the widget.

In version 0.1.2 and older, the only way to render more than one widget is by expliciting specifyng custom and unique scriptOptions.id and scriptOptions.onLoadCallbackName, with this the library injects an unique script per widget.

Something like this:

import { Turnstile } from '@marsidev/react-turnstile'

export default function Widgets() {
  return (
    <>
      <Turnstile siteKey='1x00000000000000000000AA' />

      <Turnstile 
        scriptOptions={{
          id: "second-script",
          onLoadCallbackName: "secondCallback"
        }}
        siteKey='1x00000000000000000000AA' />
    </>
  )
}

Here is an example using this strategy.

As you did not specified a custom scriptOptions.id and scriptOptions.onLoadCallbackName in the second widget, the library does not inject the second script and consequently does not run the onload function where we do the rendering.

I know that adding multiple script tags is not viable, so I changed the strategy of the script injection (PR) so in next version we can render multiple widgets with a single script tag. The only requirement is make sure each widget has an unique id:

import { Turnstile } from '@marsidev/react-turnstile'

export default function Widgets() {
  return (
    <>
      <Turnstile id='widget-1' siteKey='1x00000000000000000000AA' />
      <Turnstile id='widget-2' siteKey='1x00000000000000000000AA' />
    </>
  )
}

I included this in the docs

Also there is a new demo page where I use multiple widgets. Is not merged yet but you will be able to see it very soon at https://react-turnstile.vercel.app/multiple-widgets

aprilnickelgc commented 1 year ago

Amazing, thank you for the in depth explanation & the fix! 💪