Getting an Error - React.Children.only expected to receive a single React element child

Getting an Error as Error: React.Children.only expected to receive a single React element child.

While Integrating Internet Identity on Next.

Here’s the layout.tsx file

import { Analytics } from '@vercel/analytics/react'
import { cn } from '@/lib/utils'
import { TailwindIndicator } from '@/components/tailwind-indicator'
import { Providers } from '@/components/providers'
import { Header } from '@/components/header'
import { Toaster } from '@/components/ui/sonner'
import { KasadaClient } from '@/lib/kasada/kasada-client'
import { IdentityProvider } from '../context/AppContext'

export const metadata = {
  metadataBase: new URL('https://gemini.vercel.ai'),
  title: {
    default: 'abc',
    template: ``
  },
  description:
    'xyz',
  icons: {
    icon: '/favicon.ico',
    shortcut: '/favicon-16x16.png',
    apple: '/apple-touch-icon.png'
  }
}

export const viewport = {
  themeColor: [
    { media: '(prefers-color-scheme: light)', color: 'white' },
    { media: '(prefers-color-scheme: dark)', color: 'black' }
  ]
}

interface RootLayoutProps {
  children: React.ReactNode
}

export default function RootLayout({ children }: RootLayoutProps) {
  return (
    <html lang="en" suppressHydrationWarning>
      <body
        className={cn(
          'font-sans antialiased',
          GeistSans.variable,
          GeistMono.variable
        )}
      >
        <IdentityProvider>
          <KasadaClient />
          <Toaster position="top-center" />
          <Providers
            attribute="class"
            defaultTheme="system"
            enableSystem
            disableTransitionOnChange
          >
          
            <div className="flex flex-col min-h-screen bg-black">
            
              <Header />
              <main className="flex flex-col flex-1">{children}</main>
            
            </div>
            <TailwindIndicator />
          </Providers>
          <Analytics />
        </IdentityProvider>
      </body>
    </html>
  )
}

Providers.tsx file

'use client'

import * as React from 'react'
 import { ThemeProvider as NextThemesProvider } from 'next-themes'
import { ThemeProviderProps } from 'next-themes/dist/types'
import { SidebarProvider } from '@/lib/hooks/use-sidebar'
import { TooltipProvider } from '@/components/ui/tooltip'

export function Providers({ children, ...props }: ThemeProviderProps) {
  return (
    <NextThemesProvider {...props}>
      <SidebarProvider>
        <TooltipProvider>{children}</TooltipProvider>
      </SidebarProvider>
    </NextThemesProvider>
  )
}

Been stuck here for long. Can anybody help?
Thanking you in advance

Can you please double-check where the error is coming from?

The ThemesProvider from the next-themes library can accept multiple children elements. There are many NextJS examples showing so.

I assume that the error is coming from another provider (IdentityProvider, SidebarProvider, TooltipProvider). However, without looking into the custom code of the other providers, it is difficult to pinpoint which one allows only one child.

Yeah, you’re right. It seems like the issue is coming from the IdentityProvider, as it likely accepts only one child

SiderbarProvider code →

'use client'

import * as React from 'react'

const LOCAL_STORAGE_KEY = 'sidebar'

interface SidebarContext {
  isSidebarOpen: boolean
  toggleSidebar: () => void
  isLoading: boolean
}

const SidebarContext = React.createContext<SidebarContext | undefined>(
  undefined
)

export function useSidebar() {
  const context = React.useContext(SidebarContext)
  if (!context) {
    throw new Error('useSidebarContext must be used within a SidebarProvider')
  }
  return context
}

interface SidebarProviderProps {
  children: React.ReactNode
}

export function SidebarProvider({ children }: SidebarProviderProps) {
  const [isSidebarOpen, setSidebarOpen] = React.useState(true)
  const [isLoading, setLoading] = React.useState(true)

  React.useEffect(() => {
    const value = localStorage.getItem(LOCAL_STORAGE_KEY)
    if (value) {
      setSidebarOpen(JSON.parse(value))
    }
    setLoading(false)
  }, [])

  const toggleSidebar = () => {
    setSidebarOpen(value => {
      const newState = !value
      localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(newState))
      return newState
    })
  }

  if (isLoading) {
    return null
  }

  return (
    <SidebarContext.Provider
      value={{ isSidebarOpen, toggleSidebar, isLoading }}
    >
      {children}
    </SidebarContext.Provider>
  )
}

tooptipProvider

'use client'

import * as React from 'react'
import * as TooltipPrimitive from '@radix-ui/react-tooltip'

import { cn } from '@/lib/utils'

const TooltipProvider = TooltipPrimitive.Provider

const Tooltip = TooltipPrimitive.Root

const TooltipTrigger = TooltipPrimitive.Trigger

const TooltipContent = React.forwardRef<
  React.ElementRef<typeof TooltipPrimitive.Content>,
  React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
>(({ className, sideOffset = 4, ...props }, ref) => (
  <TooltipPrimitive.Content
    ref={ref}
    sideOffset={sideOffset}
    className={cn(
      'z-50 overflow-hidden rounded-lg bg-primary px-3 py-1.5 text-xs text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
      className
    )}
    {...props}
  />
))
TooltipContent.displayName = TooltipPrimitive.Content.displayName

export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }

and IdentityProvider

'use client'

import React, {
    createContext,
    ReactNode,
    useContext,
    useState,
    SetStateAction,
    useEffect,
    useMemo,
} from "react";
import { createAuthClient } from '../lib/auth';

const defaultValue = {
    identity: "",
    setIdentity: (): void => {},
};

export const IdentityContext = createContext<{
    identity: string;
    setIdentity: React.Dispatch<SetStateAction<string>>;
}>(defaultValue);

interface Props {
    children: ReactNode;
}

export const IdentityProvider = ({ children }: Props) => {
    const [identity, setIdentity] = useState("");
    const value = useMemo(() => ({ identity, setIdentity }), [identity]);

    useEffect(() => {
        createAuthClient()
            .then((authClient) => {
                const existingIdentity = authClient
                    .getIdentity()
                    .getPrincipal()
                    .toString();
                if(existingIdentity && existingIdentity !== "2vxsx-fae"){
                    setIdentity(existingIdentity);
                }
            })
        .catch((err) => console.log(err));
    }, [identity]);

    return (
        <IdentityContext.Provider value={value}>
            {useMemo(
                () => (
                    <>{children}</>
                ),
                [children]
            )}
        </IdentityContext.Provider>
    );
};

// eslint-disable-next-line react-refresh/only-export-components
export const useIdentity = () => useContext(IdentityContext);
{/*export const useIdentity = () => {
    return useContext(IdentityContext);
}*/}
console.log(useIdentity)
console.log(typeof useIdentity)

From an initial glance, it looks like it does accept multiple children?

But when I remove the IdentityProvider, everything works fine. It seems to be causing the issue.

Wrap the children in a react fragment.

Either

<>
   <KasadaClient />
   <Toaster position="top-center" />
  // ...
</>

or

import {Fragment} from 'react';
<Fragment>
   <KasadaClient />
   <Toaster position="top-center" />
  // ...
</Fragment>
1 Like

Nope still the same error

I think I’ve pinpointed the issue—it looks like the error is coming from here. Any chance you could take a quick look?

header.tsx

'use client'

/* eslint-disable @next/next/no-img-element */
import * as React from 'react'
import Link from 'next/link'

import { cn } from '@/lib/utils'
import { auth } from '@/auth'
import { Button, buttonVariants } from '@/components/ui/button'
import {
  IconGitHub,
  IconNextChat,
  IconSeparator,
  IconVercel
} from '@/components/ui/icons'
import { UserMenu } from '@/components/user-menu'
import { SidebarMobile } from './sidebar-mobile'
import { SidebarToggle } from './sidebar-toggle'
import { ChatHistory } from './chat-history'
import { Session } from '@/lib/types'
import { LoginButton } from './login-button'
import { IILogin, IILogout } from '../lib/auth';
import { useIdentity } from '../context/AppContext';

async function UserOrLogin() {
  const session = (await auth()) as Session
  return (
    <>
      {session?.user ? (
        <>
          <SidebarMobile>
            <ChatHistory userId={session.user.id} />
          </SidebarMobile>
          <SidebarToggle />
        </>
      ) : (
        <Link href="/new" rel="nofollow">
          <img className="size-6" src="/images/gemini.png" alt="gemini logo" />
        </Link>
      )}
      <div className="flex items-center">
        {session?.user ? (
          <UserMenu user={session.user} />
        ) : (
          <Button variant="link" asChild className="-ml-2">
            <Link href="/">internet-identity</Link>
          </Button>
        )}
      </div>
    </>
  )
}

export function Header() {

  const { identity, setIdentity } = useIdentity();

  const handleLogout = () => {
    IILogout().then(() => {
      setIdentity("");
    });
  };

  const handleConnect = () => {
    IILogin().then((id) => setIdentity(id));
  };

  return (
    <header className="sticky top-0 z-50 flex items-center justify-between w-full h-16 px-4 shrink-0 bg-gradient-to-b from-background/10 via-background/50 to-background/80 backdrop-blur-xl">
      <div className="flex items-center">
        <React.Suspense fallback={<div className="flex-1 overflow-auto" />}>
          <UserOrLogin />
        </React.Suspense>
      </div>
      <div className="flex items-center justify-end gap-2">

        {identity ? (
          <Button asChild size="sm" className="rounded-lg gap-1">
            <span onClick={handleLogout} className="hidden sm:block">{identity.slice(0, 6) + "..." + identity.slice(-4)}</span>
            <span onClick={handleLogout} className="sm:hidden">{identity.slice(0, 6) + "..." + identity.slice(-4)}</span>
          </Button>
        ) : (
          <Button asChild size="sm" className="rounded-lg gap-1">
            <span onClick={handleConnect} className="hidden sm:block">Connect Wallet</span>
            <span onClick={handleConnect} className="sm:hidden">Connect Wallet</span>
          </Button>
        )}

      </div>
    </header>
  )
}