0

I have a form with a meeting booking inside it. Part of this is a calendar component, that has a fairly complex UI.

<Calendar fullName={fullName} email={email} onEventScheduled={onEventScheduled} />

This works fine.

As React users will know, react re-renders the form when any of the inputs change. As the calendar is only dependent on some of the inputs (fullName, email and onEventScheduled), but not others, and is slow to draw, I'd like to use useMemo() to stop the calendar re-rendering:

(updated to use JSX per @dmitryguzeev comment)

  const MemoizedCalendar = useMemo(() => {
    return <Calendar fullName={fullName} email={email} onEventScheduled={onEventScheduled} />;
  }, [email, fullName, onEventScheduled]);

And then later

<MemoizedCalendar fullName={fullName} email={email} onEventScheduled={onEventScheduled} />

However swapping to MemoizedCalendar gives the following error:

Error: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: object.

Check the render method of `Formik`.

(I happen to be using Formik, however the component works fine with Calendar per above, just MemoizedCalendar fails)

Edit: I've also tried useCallback() after reading this article on UseMemo()

It is worth saying that if a component receives a function as a prop, you need to use the useCallback hook, so that it only declares the function again when necessary. Otherwise props will be different every render, since the function prop will always have a new reference.

const MemoizedCalendar = useCallback(() => {
    return Calendar(email, fullName, onEventScheduled);
  }, [email, fullName, onEventScheduled]);

Which fixes the error, but still re-renders unnecessarily when items outside email, fullName, and onEventScheduled are modified.

How can I memoize this hook component?

mikemaccana
  • 110,530
  • 99
  • 389
  • 494
  • Calendar is a React component, not a simple class. You need to use JSX syntax inside `useMemo` for this to work: `return `. – comonadd Apr 16 '21 at 11:53
  • You also need to use the memoized value as an already rendered component rather than a component to be rendered. Do this: `const memoizedCalendar = useMemo(...)` and then in your JSX `{memoizedCalendar}` – comonadd Apr 16 '21 at 12:36
  • added an answer below – comonadd Apr 16 '21 at 12:43

1 Answers1

3

To memoize your component JSX tree parts using useMemo, you need to first render it separately inside of useMemo:

// I usually prefix the name of this variable with rendered* so
// that the user below knows how to use it properly
const renderedCalendar = useMemo(() => {
  // This code below tells react to render Calendar with given props.
  // This JSX will be transformed into a function call and it will
  // happen only after the dependency array given to useMemo changes.
  return <Calendar fullName={fullName} email={email} onEventScheduled={onEventScheduled} />
}, [fullName, email, onEventScheduled]);

and then use it as a value rather than as a component in your JSX tree:

return (
  <div>
    ...
    {renderedCalendar}
  </div>
);
comonadd
  • 1,822
  • 1
  • 13
  • 23
  • One thing to remember here is that you should wrap the onEventScheduled function with useCallback hook. Else this will get re-render anyway due to the onEventScheduled function reference change in each render const onEventScheduled = useCallback( // put your function definition here , []) – SajithK Jun 02 '22 at 06:38