Home > Blockchain >  ReactDOM.render alternative due to react 18 error
ReactDOM.render alternative due to react 18 error

Time:12-16

In my app I am rendering notifications using ReactDOM.render:

ReactDOM.render(
  <NotificationsManager
    setNotify={(notifyFn) => {
      notify = notifyFn;
    }}
  />,
  containerElement
);

This however gives me error:

Warning: ReactDOM.render is no longer supported in React 18. Use createRoot instead. Until you switch to the new API, your app will behave as if it's running React 17. Learn more: https://reactjs.org/link/switch-to-createroot

If this would be the root app element I would resolve it, however this is completely different component. Is there any alternative for this case scenario?

If I try to fix it doing this:

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
  <NotificationsManager
    setNotify={(notifyFn) => {
      notify = notifyFn;
    }}
  />,
  containerElement
);

I am getting following error:

Warning: You passed a container to the second argument of root.render(...). You don't need to pass it again since you already passed it to create the root.
Warning: You are calling ReactDOMClient.createRoot() on a container that has already been passed to createRoot() before. Instead, call root.render() on the existing root instead if you want to update it.

Unfortunately calling root.render() on existing root is not possible due to the app setup.

Is there any way around this? Thank you for all your kind help.

CodePudding user response:

The following:

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
  <NotificationsManager
    setNotify={(notifyFn) => {
      notify = notifyFn;
    }}
  />,
  containerElement
);

should be:

// assuming that you had previously declared the root element:
const containerElement = document.getElementById("root");
//

const root = ReactDOM.createRoot(containerElement);
root.render(
  <NotificationsManager
    setNotify={(notifyFn) => {
      notify = notifyFn;
    }}
  />
);

As the error message says:

You don't need to pass it again since you already passed it to create the root.

So you needn't pass it again. Your question mentions containerElement, but I'm assuming you've already created that variable with the value: document.getElementById("root")

CodePudding user response:

As I understood you have a different div in your index.html file to render the NotificationManager notifications. In other words you have the following structure:

  <body>
    <noscript>You need to enable JavaScript to run this app</noscript>
    <div id="root"></div>
    <div id="notification-manager"></div>
  </body>

If I'm right you can do the following:

  • Index.jsx:
const container = document.getElementById('root'),
  root = createRoot(container);

root.render(
  <StrictMode>
    <Provider store={store}>
      <BrowserRouter>
        <App />
      </BrowserRouter>
    </Provider>
  </StrictMode>
);
  • Create a Portal component which will inject the required HTML element into the DOM as a sibling of your root div, if it doesn't exist
import { useEffect, useState } from 'react';
import { createPortal } from 'react-dom';

function createWrapperAndAppendToBody(wrapper, wrapperElementId) {
  const wrapperElement = document.createElement(wrapper);
  wrapperElement.setAttribute('id', wrapperElementId);
  document.body.appendChild(wrapperElement);
  return wrapperElement;
}

const Portal = ({ children, wrapperElement, wrapperElementId }) => {
  const [wrapper, setWrapper] = useState(null);

  useEffect(() => {
    let element = document.getElementById(wrapperElementId);
    // if element is not found with wrapperElementId or wrapperElementId is not provided,
    // create and append to body
    if (!element) {
      element = createWrapperAndAppendToBody(wrapperElement, wrapperElementId);
    }
    setWrapper(element);
  }, [wrapperElementId, wrapperElement]);

  // wrapper state will be null on the first render.
  if (wrapper === null) return null;

  return createPortal(children, wrapper);
};

export default Portal;
  • Utilize the portal component in your NotificationManger:
const NotificationsManager = ({ setNotify }) => {
  //your logic

  return (
    <Portal wrapperElement="div" wrapperElementId="notification-manager">
      {/*your jsx*/}
    </Portal>
  );
};

export default NotificationsManager;
  • Use the NotificationManger in your App.jsx:
const App = () => (
  <div className='app'>
    <NotificationsManager
      setNotify={(notifyFn) => {
        notify = notifyFn;
      }}
    />
  </div>
);
  • Related