reactjsreact-hooksapollo-client

Issue with new React 18 initialization and ApolloProvider component


I have the following index.js in my React 18 + Apollo Client demo project:

index.js:

import { createRoot } from 'react-dom/client';
import { ApolloClient, InMemoryCache, ApolloProvider } from '@apollo/client';
import App from './App';

// Initialize Apollo Client
const client = new ApolloClient({
  uri: 'http://localhost:5000/graphql',
  cache: new InMemoryCache()
});

// I have also tried with document.getElementById('root') 
// which exists at the public/index.html yet it also fails
const container = document.getElementById('app');
console.log("container: "+container);
const root = createRoot(container); 

root.render(
  <ApolloProvider client={client}>
    <App />
  </ApolloProvider>,
);

The new React 18 requires to use a different initialization process, so I try to follow the recommendations from https://react.dev/blog/2022/03/08/react-18-upgrade-guide#updates-to-client-rendering-apis

So I created the App component as follows:

import React from 'react';
import ECGGraph from './ECGGraph';

function App() {
  return (
    <div className="App" id="App">
      <h1>ECG Data of Alice Johnson</h1>
      <ECGGraph />
    </div>
  );
}

export default App;

The ECGGraph is long but essentially needs to use the context of the ApolloProvider to render data to the UI.

Yet the problem is that I cannot make react to initialize as it shows the following error:

Error: createRoot(...): Target container is not a DOM element.
    at createRoot (react-dom.development.js:29384:1)
    at Object.createRoot$1 [as createRoot] (react-dom.development.js:29855:1)
    at exports.createRoot (client.js:12:1)
    at ./src/index.js (index.js:13:1)
    at options.factory (react refresh:6:1)
    at __webpack_require__ (bootstrap:22:1)
    at startup:7:1
    at startup:7:1

Which as far as I understand because ApolloProvider wants to wrap the App, yet React 18 requires a different approach now, so what's the solutions in order to make this work?

I also tried to change the const container = document.getElementById('app'); to const container = document.getElementById('root'); If I do that, a different error at the useQuery hook appears:

react.development.js:1618 Uncaught TypeError: Cannot read properties of null (reading 'useContext')
    at Object.useContext (react.development.js:1618:1)
    at ApolloProvider (ApolloProvider.tsx:19:1)
    at renderWithHooks (react-dom.development.js:15486:1)
    at mountIndeterminateComponent (react-dom.development.js:20103:1)
    at beginWork (react-dom.development.js:21626:1)
    at HTMLUnknownElement.callCallback (react-dom.development.js:4164:1)
    at Object.invokeGuardedCallbackDev (react-dom.development.js:4213:1)
    at invokeGuardedCallback (react-dom.development.js:4277:1)
    at beginWork$1 (react-dom.development.js:27490:1)
    at performUnitOfWork (react-dom.development.js:26596:1)

Solution

  • Your code is attempting to mount the React app into a div element that itself renders. This is obviously impossible to do since the app would already need to be mounted and rendered to the DOM in order for App and <div className="App" id="App"> to be rendered in order to be targetable via document.getElementById.

    You should be targeting an element rendered in your index.html file.

    A typical /public/index.html file looks like this:

    <!DOCTYPE html>
    <html lang="en">
    
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
        <meta name="theme-color" content="#000000">
        <!--
          manifest.json provides metadata used when your web app is added to the
          homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/
        -->
        <link rel="manifest" href="%PUBLIC_URL%/manifest.json">
        <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
        <!--
          Notice the use of %PUBLIC_URL% in the tags above.
          It will be replaced with the URL of the `public` folder during the build.
          Only files inside the `public` folder can be referenced from the HTML.
    
          Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
          work correctly both with client-side routing and a non-root public URL.
          Learn how to configure a non-root public URL by running `npm run build`.
        -->
        <title>React App</title>
    </head>
    
    <body>
        <noscript>
            You need to enable JavaScript to run this app.
        </noscript>
        <div id="root"></div>
        <!--
          This HTML file is a template.
          If you open it directly in the browser, you will see an empty page.
    
          You can add webfonts, meta tags, or analytics to this file.
          The build step will place the bundled scripts into the <body> tag.
    
          To begin the development, run `npm start` or `yarn start`.
          To create a production bundle, use `npm run build` or `yarn build`.
        -->
    </body>
    </html>
    

    You target the <div id="root"></div> element and mount the React app, i.e. App, into it.

    import { createRoot } from 'react-dom/client';
    import { ApolloClient, InMemoryCache, ApolloProvider } from '@apollo/client';
    import App from './App';
    
    // Initialize Apollo Client
    const client = new ApolloClient({
      uri: 'http://localhost:5000/graphql',
      cache: new InMemoryCache()
    });
    
    const container = document.getElementById('root');
    const root = createRoot(container); 
    
    root.render(
      <ApolloProvider client={client}>
        <App />
      </ApolloProvider>,
    );