How to pass a ref to a dynamically loaded component

890 Views Asked by At

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);

1

There are 1 best solutions below

1
Alex Gourlay On BEST ANSWER

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