React component works fine, but attempting to use useMemo() results in an error

2.1k views Asked by At

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?

1

There are 1 answers

1
comonadd On BEST ANSWER

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