#Hydration error about unexpected <button> in <div> , but the component does not have a <button>

22 messages · Page 1 of 1 (latest)

wispy charm
#

I am getting the Hydration failed because the initial UI does not match what was rendered on the server. error. What's strange is that my component (CollectionStats) does not have any buttons within it.

Why would the server be rendering a button?

modern citrus
novel ice
#

You can see that the error is from styled-components. Send the code

wispy charm
#

@modern citrus @novel ice
Okay so I commented out most of the code so that it is the most minimal it can be and still throw the hydration error.

I have CollectionStats.tsx here:


import styled from "styled-components";

import { useContractInfo } from "../api/getContractInfo";
import { useContractAddress } from "../hooks/useContractAddress";

export function CollectionStats() {
  const contractAddress = useContractAddress();

  const contractInfoQuery = useContractInfo(contractAddress);

  if (contractInfoQuery.isLoading || contractInfoQuery.isError) {
    return null; // wip: proper error handling
  }

  return (
    <div></div>
  )
}
#

useContractInfo is a custom hook wrapped around react-query useQuery

#

Is it possible that react-query in this case could be causing issues? It shouldn't though because on the server, it should just returning null because by default isLoading is false yeah?


#

In terms of page.tsx I have:

import { dehydrate, Hydrate } from "@tanstack/react-query";

import { prefetchBeers } from "@/features/beers";
import { CollectionTokensPageView } from "@/features/collections";
import { getQueryClient } from "@/lib/react-query";

export default async function CollectionTokensPage({
  params,
}: {
  params: { contractAddress: string };
}) {
  const queryClient = getQueryClient();

  // add queries here...
  await prefetchBeers();

  const dehydratedState = dehydrate(queryClient);

  return (
    <Hydrate state={dehydratedState}>
      <CollectionTokensPageView />
    </Hydrate>
  );
}

CollectionTokensPageView is a client component.

#

———

#

In terms of the relevant layout.tsx I have:

import { Metadata } from "next";
import { Suspense } from "react";

import {
  CollectionInfoLoadingView,
  CollectionPageLayoutView,
} from "@/features/collections";
import { CollectionInfoFetcher } from "@/features/collections";
import { getContractInfo } from "@/features/collections";

/**
 * Metadata
 */

interface Props {
  params: {
    contractAddress: string;
  };
}

export async function generateMetadata({ params }: Props): Promise<Metadata> {
  const { contractAddress } = params;
  const contractInfo = await getContractInfo(contractAddress);

  return {
    title: contractInfo?.name || contractInfo.id,
  };
}

/**
 * Interfaces
 */
interface CollectionPageLayoutProps {
  children: React.ReactNode;
  params: {
    contractAddress: string;
  };
}

/**
 * Component
 */

export default async function CollectionPageLayout({
  children,
  params,
}: CollectionPageLayoutProps) {
  return (
    <CollectionPageLayoutView
      collectionInfoComponent={
        <Suspense fallback={<CollectionInfoLoadingView />}>
          {/* @ts-expect-error Server Component */}
          <CollectionInfoFetcher contractAddress={params.contractAddress} />
        </Suspense>
      }
    >
      {children}
    </CollectionPageLayoutView>
  );
}
novel ice
#

Are you using app directory?

wispy charm
#

Yes I"m using the new app directory

novel ice
#

Then, you need to add the “use client” to the top since it should run on client side

wispy charm
#

CollectionStats.tsx is imported inside of another component I have called CollectionInfo.tsx

#

And in CollectionInfo.tsx I marked it with "use client" already

#

So CollectionStats.tsx should be treated as a client component already

novel ice
#

Try to remove the code one by one and check which line of code caused the error

wispy charm
#

Yeah I have been. Unfortunately haven't been able to pin it down. It's super weird cause the error is talking about a <button> but I don't have any in the component it's throwing the error in

#

Extra hard to pin it down cause I can only reproduce the hydration error 1 out of every like 10-20 times

#

So have to keep hard refreshing the page until it happens

#

Okay wait I think it has to do with the react-query related code

#

This

 if (contractInfoQuery.isLoading || contractInfoQuery.isError) {
    return null; // wip: proper error handling
  }
#

Strange though, cause on the server and on the client for the first render, contractInfoQuery.isLoading should be true. So first render should match what server rendered.