简体   繁体   中英

Is it possible to expose a function defined within a React function component to be called in other components?

I'm refactoring some old code for an alert widget and am abstracting it into its own component that uses DOM portals and conditional rendering. I want to keep as much of the work inside of this component as I possibly can, so ideally I'd love to be able to expose the Alert component itself as well as a function defined inside of that component triggers the render state and style animations so that no outside state management is required. Something like this is what I'm looking to do:

import Alert, { renderAlert } from '../Alert'

const CopyButton = () => (
  <>
    <Alert text="Text copied!" />
    <button onClick={() => renderAlert()}>Copy Your Text</button>
  </>
)

Here's what I currently have for the Alert component - right now it takes in a state variable from outside that just flips when the button is clicked and triggers the useEffect inside of the Alert to trigger the renderAlert function. I'd love to just expose renderAlert directly from the component so I can call it without the additional state variable like above.

const Alert = ({ label, color, stateTrigger }) => {
  const { Alert__Container, Alert, open } = styles;

  const [alertVisible, setAlertVisible] = useState<boolean>(false);
  const [alertRendered, setAlertRendered] = useState<boolean>(false);

  const portalElement = document.getElementById('portal');

  const renderAlert = (): void => {
    setAlertRendered(false);
    setAlertVisible(false);

    setTimeout(() => {
      setAlertVisible(true);
    }, 5);
    setAlertRendered(true);

    setTimeout(() => {
      setTimeout(() => {
        setAlertRendered(false);
      }, 251);
      setAlertVisible(false);
    }, 3000);
  };

  useEffect(() => {
    renderAlert();
  }, [stateTrigger])

  const ele = (
    <div className={Alert__Container}>
      { alertRendered && (
        <div className={`${Alert} ${alertVisible ? open : ''}`}>
          <DesignLibAlert label={label} color={color}/>
        </div>
      )}
    </div>
  );

  return portalElement
    ? ReactDOM.createPortal(ele, portalElement) : null;
};

export default Alert;

Though it's not common to "reach" into other components and invoke functions, React does allow a "backdoor" to do so.

The idea is to expose out the renderAlert function imperatively via the React ref system.

Example:

import { forwardRef, useImperativeHandle } from 'react';

const Alert = forwardRef(({ label, color, stateTrigger }, ref) => {
  const { Alert__Container, Alert, open } = styles;

  const [alertVisible, setAlertVisible] = useState<boolean>(false);
  const [alertRendered, setAlertRendered] = useState<boolean>(false);

  const portalElement = document.getElementById('portal');

  const renderAlert = (): void => {
    setAlertRendered(false);
    setAlertVisible(false);

    setTimeout(() => {
      setAlertVisible(true);
    }, 5);
    setAlertRendered(true);

    setTimeout(() => {
      setTimeout(() => {
        setAlertRendered(false);
      }, 251);
      setAlertVisible(false);
    }, 3000);
  };

  useEffect(() => {
    renderAlert();
  }, [stateTrigger]);

  useImperativeHandle(ref, () => ({
    renderAlert,
  }));

  const ele = (
    <div className={Alert__Container}>
      { alertRendered && (
        <div className={`${Alert} ${alertVisible ? open : ''}`}>
          <DesignLibAlert label={label} color={color}/>
        </div>
      )}
    </div>
  );

  return portalElement
    ? ReactDOM.createPortal(ele, portalElement) : null;
});

export default Alert;

...

import { useRef } from 'react';
import Alert from '../Alert'

const CopyButton = () => {
  const ref = useRef();

  const clickHandler = () => {
    ref.current?.renderAlert();
  };

  return (
    <>
      <Alert ref={ref} text="Text copied!" />
      <button onClick={clickHandler}>Copy Your Text</button>
    </>
  )
};

编辑 is-it-possible-to-expose-a-function-defined-within-a-react-function-component-to

A more React-way to accomplish this might be to abstract the Alert state into an AlertProvider that renders the portal and handles the rendering of the alert and provides the renderAlert function via the context.

Example:

import { createContext, useContext, useState } from "react";

interface I_Alert {
  renderAlert: (text: string) => void;
}

const AlertContext = createContext<I_Alert>({
  renderAlert: () => {}
});

const useAlert = () => useContext(AlertContext);

const AlertProvider = ({ children }: { children: React.ReactElement }) => {
  const [text, setText] = useState<string>("");
  const [alertVisible, setAlertVisible] = useState<boolean>(false);
  const [alertRendered, setAlertRendered] = useState<boolean>(false);

  ...

  const renderAlert = (text: string): void => {
    setAlertRendered(false);
    setAlertVisible(false);
    setText(text);

    setTimeout(() => {
      setAlertVisible(true);
    }, 5);
    setAlertRendered(true);

    setTimeout(() => {
      setTimeout(() => {
        setAlertRendered(false);
      }, 251);
      setAlertVisible(false);
    }, 3000);
  };

  const ele = <div>{alertRendered && <div> ..... </div>}</div>;

  return (
    <AlertContext.Provider value={{ renderAlert }}>
      {children}
      // ... portal ...
    </AlertContext.Provider>
  );
};

...

const CopyButton = () => {
  const { renderAlert } = useAlert();

  const clickHandler = () => {
    renderAlert("Text copied!");
  };

  return (
    <>
      <button onClick={clickHandler}>Copy Your Text</button>
    </>
  );
};

...

function App() {
  return (
    <AlertProvider>
      ...
      <div className="App">
        ...
        <CopyButton />
        ...
      </div>
      ...
    </AlertProvider>
  );
}

编辑 is-it-possible-to-expose-a-function-defined-within-a-react-function-component-to (forked)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM