0

I'd like to pass a ref to a dynamically loaded component. When the component's loaded with a regular import everything works as expected. When using a dynamic import the ref value is undefined || { current: null }.

I get the following the error message in the console:

Warning: Function components cannot be given refs. 
Attempts to access this ref will fail. Did you mean to use React.forwardRef()?

I've attempted to use a React.forwardRef but have been unsuccessful in my attempts.

Parent Component: Recaptcha.tsx

import React from 'react';
import loadable from '@loadable/component';
import ReCAPTCHA from 'react-google-recaptcha';

export type RecaptchaProps = {
  handleOnError: () => void;
  handleOnExpire: () => void;
  forwardedRef: React.MutableRefObject<ReCAPTCHA | null>;
  error: string | null;
  load: boolean;
};

const Recaptcha: React.FunctionComponent<RecaptchaProps> = ({
  handleOnError,
  handleOnExpire,
  error,
  forwardedRef,
  // load,
}) => {
  const RecaptchaPackage = loadable(
    () => import('components/Form/Recaptcha/RecaptchaPackage'),
  );

  return (
    <div>
      <RecaptchaPackage
        handleOnError={handleOnError}
        handleOnExpire={handleOnExpire}
        forwardedRef={forwardedRef}
      />
    </div>
  );
};

export default React.memo(Recaptcha);

Dynamic Component: RecaptchaPackage.tsx

import React from 'react';
import ReCAPTCHA from 'react-google-recaptcha';

export type RecaptchaPackageProps = {
  handleOnError: () => void;
  handleOnExpire: () => void;
  forwardedRef: React.MutableRefObject<ReCAPTCHA | null>;
};

const RecaptchaPackage: React.FunctionComponent<RecaptchaPackageProps> = ({
  handleOnError,
  handleOnExpire,
  forwardedRef,
}) => {
  return process.env.GATSBY_RECAPTCHA_PUBLIC ? (
    <ReCAPTCHA
      onErrored={handleOnError}
      onExpired={handleOnExpire}
      ref={forwardedRef}
      sitekey={process.env.GATSBY_RECAPTCHA_PUBLIC}
      size="invisible"
    />
  ) : null;
};

export default React.memo(RecaptchaPackage);

Ferran Buireu
  • 28,630
  • 6
  • 39
  • 67
  • According to the loadable docs, the loadable call should be done outside of the component, so would try that. And yeah `forwardRef` is used to pass a ref from child to parent so you're right, doesn't help here. – Alex Gourlay Sep 28 '22 at 12:52
  • I tried moving the loadable function outside the component, however, it still gets the same result. – Lewis Carville Sep 28 '22 at 12:56
  • Besides the ref returning undefined the dynamically loaded component is correctly loading the recaptcha script. Without a valid ref however I’m unable to execute any functions with it. – Lewis Carville Sep 28 '22 at 13:06
  • I had another look at your code and actually `forwardsRef` is the right choice, as you are passing the ReCAPTCHA ref upwards. – Alex Gourlay Sep 28 '22 at 13:38

1 Answers1

1

You're looking to pass a ref of the ReCAPTCHA component (from 'react-google-recaptcha') up through two parent elements. For this you can make use of React's forwardRef.

Another thing to note is that the ref will not always be assigned a value on component mount, this is true when using with and without dynamic loading. So if you want to log the ref for testing, log in a setTimeout or some other event that is triggered later.

Your Recaptcha component would look like this:

import React from "react";
import loadable from "@loadable/component";
import ReCAPTCHA from "react-google-recaptcha";

export type RecaptchaProps = {
  handleOnError: () => void;
  handleOnExpire: () => void;
  error: string | null;
  load: boolean;
};

const RecaptchaPackage = loadable(() => import("./RecaptchaPackage"));

const Recaptcha: React.FunctionComponent<RecaptchaProps> = React.forwardRef(({
  handleOnError,
  handleOnExpire,
  error,
  // load,
}, ref) => {
  return (
    <div>
      <RecaptchaPackage
        ref={ref}
        handleOnError={handleOnError}
        handleOnExpire={handleOnExpire}
      />
    </div>
  );
})

export default React.memo(Recaptcha);

And your RecaptchaPackage component would look like this:

import React from "react";

import ReCAPTCHA from "react-google-recaptcha";

export type RecaptchaPackageProps = {
  handleOnError?: () => void;
  handleOnExpire?: () => void;
};

const RecaptchaPackage: React.FunctionComponent<RecaptchaPackageProps> = React.forwardRef(({
  handleOnError,
  handleOnExpire
}, ref) => {
  return process.env.GATSBY_RECAPTCHA_PUBLIC ? (
    <ReCAPTCHA
      ref={ref}
      onErrored={handleOnError}
      onExpired={handleOnExpire}
      sitekey={process.env.GATSBY_RECAPTCHA_PUBLIC}
      size="invisible"
    />
  ) : null);
});

export default React.memo(RecaptchaPackage);

I've created a sandbox that validates that this should work: https://playcode.io/972680

Alex Gourlay
  • 365
  • 1
  • 12
  • Made the necessary changes to the project and all is working now. Thanks for explanation and the code example, very helpful! – Lewis Carville Sep 28 '22 at 13:56