简体   繁体   中英

How can I expose functions to the ref using functional components?

I want to create a component lets say a calendar functional component which can expose some functions like nextMonth , prevMonth , etc. So that the parent component using it can access these functions using the ref . It was easy with class components as the instance methods are automatically exposed. How do I achieve the same with functional components? I know I can use render props pattern for this. But any other solution?

Rough example:

function Calendar() {
   const nextMonth = () => {...}
   const prevMonth = () => {...}

   return (
      <div>.....</div>
   )
}


function Parent() {
   const cal = createRef();

   const onNext = () => {
     cal.current.nextMonth();
   }

   return (
     <>
        <Calendar ref={cal} />
        <button onClick={onNext}>Next</button>
     </>
   )
}

You can use useImperativeHandle hook.

const Calendar = React.forwardRef((props, ref) => {
  const [month, setMonth] = useState(0)
  const nextMonth = () => { setMonth((prev) => prev + 1) }
  const prevMonth = () => { setMonth((prev) => prev - 1) }

  useImperativeHandle(ref, () => ({
    nextMonth,
    prevMonth
  }));

  return (
    <div>{month}</div>
  )
})

const Parent = () => {
  const cal = useRef()

  const onNext = () => {
    cal.current.nextMonth()
  }

  return (
    <React.Fragment>
      <Calendar ref={cal} />
      <button type="button" onClick={onNext}>Next</button>
    </React.Fragment>
  )
}

Here's how you can do it, even though it's not really the React way. The trick is to set the current value of the ref in Calendar .

const Calendar = React.forwardRef((props, ref) => {
  const [month, setMonth] = useState(0)
  const nextMonth = () => { setMonth((prev) => prev + 1) }
  const prevMonth = () => { setMonth((prev) => prev - 1) }

  ref.current = { nextMonth, prevMonth } // eslint-disable-line

  return (
    <div>{month}</div>
  )
})

const Parent = () => {
  const cal = useRef()

  const onNext = () => {
    cal.current.nextMonth()
  }

  return (
    <React.Fragment>
      <Calendar ref={cal} />
      <button type="button" onClick={onNext}>Next</button>
    </React.Fragment>
  )
}

in functional components you can use forwardRef to pass ref to child component.

const Calendar = React.forwardRef((props, ref) => {
  const nextMonth = () => {...};
  const prevMonth = () => {...};

  return <div>.....</div>;
});

function Parent() {
  const cal = useRef(null);

  const onNext = () => {
    cal.current.nextMonth();
  };

  return (
    <>
      <Calendar ref={cal} />
      <button onClick={onNext}>Next</button>
    </>
  );
}

this work:

const Calendar = React.forwardRef((props, ref) => {
  const nextMonth = () => console.log("nextMonth");
  const prevMonth = () => console.log("prevMonth");
  ref.current = { nextMonth, prevMonth };
  return <div>.....</div>;
});

function Parent() {
  const cal = useRef(null);

  const onNext = () => {
    cal.current.nextMonth();
  };

  return (
    <>
      <Calendar ref={cal} />
      <button onClick={onNext}>Next</button>
    </>
  );
}

While @Matthieu Libeer answer is correct and this should work to you still consider making that in React way. By now your Calendar is the same uncontrolled component . By accessing it's internal state(and methods in case of class-based components) by ref you are loosing:

  1. Ability to make pre-input validation(say, to discard selection for each second Sunday in month)
  2. Replace it with different component in easy way

Once it would be fully controllable component + keeping consistency with native controls in props' names you would get something like

<Calendar value={currentData} onChange={this.validateAndSetDate} />

This way it can be easily replaced with compatible component(say <input type="date" /> ) as well as validation is much more easier. You still might need forwardRef say to .focus() imperatively but there will be less pain with other aspects.

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