Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[bug] using WAGMI with SSR (Next JS) is causing styling issues #542

Closed
1 task done
kris10cabrera opened this issue Jun 1, 2022 · 49 comments · Fixed by #1040
Closed
1 task done

[bug] using WAGMI with SSR (Next JS) is causing styling issues #542

kris10cabrera opened this issue Jun 1, 2022 · 49 comments · Fixed by #1040

Comments

@kris10cabrera
Copy link

Is there an existing issue for this?

  • I have searched the existing issues

Package Version

0.3.5

Current Behavior

(copying from a discussion we previously created)

After bumping wagmi to 0.3.5 in our project, we started to face a number of styling issues (here's an example 😆) due to a mismatch between the server-rendered HTML and the first render on the client side.

After some investigation, I discovered that this was due to hooks like useAccount returning isLoading as true during SSR, but as false on the client. Here's an example of the return value of useAccount during SSR and on the client:

Before we upgraded to 0.3, the SSR and client output was consistent on first render. In this case it returned:

[
  {
    "loading": false
  },
  null
]

A few questions:

  • Why is loading true on the server side. In my tests, it's also true when autoConnect is set to false?
  • Is there a recommended pattern for handling SSR in wagmi? Currently we're manually patching this issue in many places, but I would prefer to help with a fix in wagmi 😄

I would guess that anyone using Next.js + WAGMI + Stitches will face a similar issue to us.

Expected Behavior

No response

Steps To Reproduce

Styling bug isn't visible in the repo to reproduce, but mis-match between client/server output is highlighted in a console error.

Link to Minimal Reproducible Example (CodeSandbox, StackBlitz, etc.)

https://github.com/smhutch/wagmi-sandbox/blob/main/README.md

Anything else?

No response

@jxom
Copy link
Member

jxom commented Jun 1, 2022

Hey!

Sorry, I completely missed the discussion thread, thanks for opening an issue here (definitely easier to keep track).

Yeah, there are a couple of nuances of server/client hydration in wagmi. The main culprit of these hydration issues is that wagmi now caches responses and persists them to local storage (which is obviously not accessible on the server). This is not just a wagmi issue, but also for any library that persists state to local/session storage. Currently, there is no first-class solution for SSR in wagmi (but this is something that is on the roadmap – perhaps using cookies).

There are a couple of workarounds to resolve these hydration issues right now that have their trade-offs:

  1. You can use a useIsMounted Hook and render your content after hydration. This can be seen in our examples. The trade-off here is content nested within the isMounted variable is not visible on the server.
function Page() {
  const isMounted = useIsMounted()

  if (!isMounted) return null
  return (
    ...
  )
}
  1. Otherwise, you can guard wagmi data in an isMounted variable. The trade-off here is that it's a bit annoying to have isMounted variables floating everywhere.
function Example() {
  const isMounted = useIsMounted();
  const { data } = useAccount();
  
  return (
    ...
    { isMounted && <div>{data?.address}</div> }
    ...
  )
}

3 (NOT RECOMMENDED). You can turn off local storage persistance completely, which will resolve everything, but comes with the trade-off that you lose data persistence on page load (and will consequently see FOUW – flash of unconnected wallet, balances, contract data, etc)

const client = createClient({
  ...
  persister: null
})

Why is loading true on the server side. In my tests, it's also true when autoConnect is set to false?

This is probably a bug on the wagmi side - I'll take a look!

@farreldarian
Copy link
Contributor

  1. You can turn off local storage persistance completely, which will resolve everything, but comes with the trade-off that you lose data persistence on page load (and will consequently see FOUW – flash of unconnected wallet/account)

It seems like with the current version, the persister and storage can't be set to null.

@robyn3choi
Copy link

Setting persister = null in createClient is no longer preventing the hydration errors for me after updating from wagmi version 0.4.12 to 0.5.10

@blgaman
Copy link

blgaman commented Jul 22, 2022

next example in wagmi also has the same issue.

image

connector needs isMounted like the below.

-- {isConnected && (
++ {isMounted && isConnected && (
  <button onClick={() => disconnect()}>
      Disconnect from {connector?.name}
  </button>
)}

@kris10cabrera
Copy link
Author

@farreldarian i saw this was merged nice #714, i imagine this fix isn't available until the next release correct?

@tmm
Copy link
Member

tmm commented Jul 25, 2022

Correct! Will go out in the next release.

@QuintonC
Copy link
Contributor

This is still present for me, both when using isMounted and when defining persister: null.

It also appears that the latest release, wagmi": "^0.6.3 has broken some stuff as it now requires @tanstack/react-query

Screen Shot 2022-08-14 at 12 49 13 PM

@ottodevs
Copy link

ottodevs commented Aug 22, 2022

This is not fixed, the issue should be kept open

@jvalentee
Copy link

I'm also getting this bug and I'm using "wagmi": "^0.6.4".
isMounted and persister: null do not solve the issue.

@QuintonC
Copy link
Contributor

Hey @tmm 👋

Any chance we can get this re-opened? This issue isn't fixed quite yet.

@sb777
Copy link

sb777 commented Sep 14, 2022

I was also not able to fix the hydration error with "wagmi": "^0.6.6" using either isMounted or persister: null.

What is currently working for me is useEffect, like so:

const { address, connector, isConnected } = useAccount()

const [connected, setConnected] = useState(false)

useEffect(() => {
  setConnected(isConnected);
}, [isConnected]);

then

My address is: {connected && address}

Shout out to @ottodevs for this workaround. Hope that helps someone!

@imornar
Copy link

imornar commented Sep 22, 2022

This issue still persist, it's the only thing stopping me from using this wonderful lib, I really hate keep dragging isMounted hook thru my codebase, it would be awesome if you guys can prioritize this higher.

@tmm
Copy link
Member

tmm commented Sep 22, 2022

@imornar first-class SSR support is on the roadmap, but we are holding until we see the Next.js Layouts RFC progress a little more (lots of folks use wagmi and Next.js together and we want to make sure wagmi's SSR API design is compatible with the future of Next).

In the meantime, you're welcome to take some of the ideas from #689 — pass server-compatible storage to client and hydrate client state using client.setState — if you are in need of an immediate solution.

@apecollector
Copy link

@tmm Shouldn't there be like a big notice or warning that currently wagmi does not work with next.js, the most popular react framework? I finally got here after reading a bunch of issues and discussions of work arounds that had previously worked but do not as of the latest wagmi release.

The example in ./examples/next shows the error:

Screen Shot 2022-10-04 at 9 14 08 PM

@razgraf
Copy link
Contributor

razgraf commented Oct 4, 2022

A temporary solution while SSR is not resolved, if you're ok with some "middleware" is to create a standalone context in your next-app where you interpret the data from wagmi. It sits between wagmi and the app itself.

Basically, you create a provider and a context state more or less similar to ReturnType<typeof useAccount>. You can later use your own hook that accesses that state (your custom useAccount). Based on a "mounted" flag, that context can return default values (while things are loading) or actual data from the wagmi state.

Advantages:

  • You don't have to disable persister
  • You get to interpret the data you get from wagmi
  • You create a middle layer to hold the account data such that you don't always ask for the isMounted state inline
  • When SSR is going to be fixed, the implementation is still compatible

@tmm
Copy link
Member

tmm commented Oct 4, 2022

@apecollector wagmi does work with Next.js.

A first-class API would be nice, but like I said in #542 (comment), we are waiting a bit to see how the Layouts RFC progresses. In general, if you see hydration/text mismatches, you need to handle them at the app-level and follow good SSR practices.

@apecollector
Copy link

@apecollector wagmi does work with Next.js.

Not in the included example or according to others is this issue who has tried various solutions without being able to get wagmi working with next.js. If it works, why not update the example so it doesn't result in an error.

#542 (comment)
#542 (comment)

@jxom
Copy link
Member

jxom commented Oct 5, 2022

@apecollector – we can update the examples.

Update: Done. (#1040)

@SimRunBot
Copy link

issue still persists in "wagmi": "^0.6.8"
Workaround with isMounted works

@scherroman
Copy link

For the isMounted fix, I found that neither the useIsMounted hook from usehooks-ts nor the useMountedState from react-use worked as both use a ref internally that is only set to true inside a useEffect, which runs after the rest of the content has finished rendering on the client. And since it's a ref, updating it doesn't trigger a rerender itself, so nothing shows up.

To get it to work I had to write a simple useIsMounted hook that uses setState instead so that a rerender is triggered:

import { useState, useEffect } from 'react'

export function useIsMounted(): boolean {
    let [isMounted, setIsMounted] = useState(false)

    useEffect(() => {
        setIsMounted(true)
    }, [])

    return isMounted
}

@thanks173
Copy link

thanks173 commented Nov 1, 2022

Filling the codebase with the isMounted is really annoying for me.
And I think I found the workaround solution that just works.

For anyone who is facing the "Hydration failed" problem, maybe you could give it a try.

const client = createClient({
  autoConnect: false,
  provider,
  webSocketProvider,
})
  const { connectAsync, connectors } = useConnect()
  const client = useClient()

  const [isAutoConnecting, setIsAutoConnecting] = useState(false)

  useEffect(() => {
    if (isAutoConnecting) return
    if (isConnected) return

    setIsAutoConnecting(true)

    const autoConnect = async () => {
      const lastUsedConnector = client.storage?.getItem("wallet")

      const sorted = lastUsedConnector ?
        [...connectors].sort((x) =>
          x.id === lastUsedConnector ? -1 : 1,
        )
        : connectors

      for (const connector of sorted) {
        if (!connector.ready || !connector.isAuthorized) continue
        const isAuthorized = await connector.isAuthorized()
        if (!isAuthorized) continue

        await connectAsync({ connector })
        break
      }
    }

    autoConnect()
  }, [])

I found that the autoConnect feature is the root cause, so I handle that myself. Ref: interal autoconnect function

@Hidde-Heijnen
Copy link

Also having this problem, I read that you wanted to explore this when next13 arrived, and since it is now in beta, I was wondering if you have an ETA on this feature? Kinda sucks to have to use workarounds in my apps, especially now since web3modal changed to the wagmi hooks in v2 (the previous hooks didn't face this problem) and a lot of extra users will be facing these problems now.

Thanks in advance! @jxom

@kombos
Copy link

kombos commented Feb 1, 2023

how is this still not fixed? we are in wagmi@0.10.10 now. Please do fix this soon. Tx.

@tmm
Copy link
Member

tmm commented Feb 1, 2023

This is not a wagmi issue that needs fixing. You should follow SSR best practices for your site. Once the Next.js app directory is out of beta, we will look into adding a first-class integration in wagmi to make this easier.

@llllvvuu
Copy link
Contributor

llllvvuu commented Feb 19, 2023

An easy way to save developers from having to do isMounted manually from every callsite (i.e. "SSR hygiene") could be to add a third non-boolean option for autoconnect, e.g.

export const client = createClient({
  autoConnect: "onlyAfterHydration",
  connectors,
  provider,
})

In the meantime, developers will probably have to put wrapper hooks around useAccount, etc that return disconnected during hydration

(FWIW, autoconnect has always happened to be slow enough for me that I've not run into this issue)

@tmm
Copy link
Member

tmm commented Feb 19, 2023

You could set autoConnect to false and create your own hook/utility to auto-connect after hydration.

@montanaflynn
Copy link

@tmm that's what everyone is currently doing, but there's an option called autoConnect and it would be nice to just use it instead of having multiple different implementations of the same thing. Next is the most popular framework at the moment and I'd guess more than half of people using wagmi will try to use it with next.js at some point.

@tmm
Copy link
Member

tmm commented Feb 20, 2023

Again, once the Next.js app directory is out of beta, we can add more streamlined support. Until then, we are focused on other efforts.

@jxom
Copy link
Member

jxom commented Feb 20, 2023

@llllvvuu – unfortunately, autoConnect only solves the account & active chain case... You'd still see hydration errors for cached & persisted contract data, balances, and everything else on-chain that is not account specific etc...

@jxom
Copy link
Member

jxom commented Feb 20, 2023

Keep in mind, this problem is not wagmi specific – it exists for anything persisted using Web Storage with Next.js. There will eventually be a first-class solution to deal with SSR & rehydration in wagmi – so please be patient.

@anthonyjacquelin
Copy link

anthonyjacquelin commented Mar 15, 2023

Again, once the Next.js app directory is out of beta, we can add more streamlined support. Until then, we are focused on other efforts.

@tmm So do you agree that for now we cannot use Wagmi with Next 13 + App folder ?

Do you recommend to use the pages directory instead ?

I face a whole big issue because of that, see: #939

@tmm
Copy link
Member

tmm commented Mar 15, 2023

It's your call. The app directory is beta software, but should work fine if you follow hydration best-practices.

@anthonyjacquelin
Copy link

anthonyjacquelin commented Mar 15, 2023

@tmm

My whole app is using next.js 13 with the app folder that next.js released.

I can't use anymore the ready state here:

  {ready ? (
        <WagmiConfig client={wagmiClient}>
          <Component {...pageProps} />
        </WagmiConfig>
      ) : null}

as it causes me the error that the whole app loading depends on this state which is client side and so nothing loads until that moment where the state is "ready" and so it gives me this error: #46968

My SEO using the Metadata API of next doesn't work because everything is basically a server side component, and the state cannot be compute in server component.

Here is what i get if i remove the ready state :
Capture d’écran 2023-03-14 à 18 36 18

Is it also related to hydration ?

@Electornic
Copy link

I think it's okay to modify it like this.

import { useEffect } from 'react';
import { createClient } from 'wagmi';

const client = createClient({
  autoConnect: false,
  connectors,
  provider
})

export default function App({ Component, pageProps }: AppPropsWithLayout) {
  useEffect(() => {
    client.autoConnect();
  }, [])

  return (
    ...
  )
}

@liangtfm
Copy link

Following the updated example nextjs app https://github.com/wagmi-dev/wagmi/blob/main/examples/_dev/src/pages/index.tsx, this would cause a brief flash of a blank page when navigating between routes. Does anyone have a workaround/fix for this that doesn't cause the flash?

@llllvvuu
Copy link
Contributor

Following the updated example nextjs app https://github.com/wagmi-dev/wagmi/blob/main/examples/_dev/src/pages/index.tsx, this would cause a brief flash of a blank page when navigating between routes. Does anyone have a workaround/fix for this that doesn't cause the flash?

You need to make the workaround more precise. For example, instead of hiding the entire app on initial render, just wrap the return values of hooks.

@llllvvuu
Copy link
Contributor

FWIW I suspect that the inconsistent values on initial render is related to the usage of a global config variable, which will be gone in wagmi@2

@liangtfm
Copy link

Following the updated example nextjs app https://github.com/wagmi-dev/wagmi/blob/main/examples/_dev/src/pages/index.tsx, this would cause a brief flash of a blank page when navigating between routes. Does anyone have a workaround/fix for this that doesn't cause the flash?

You need to make the workaround more precise. For example, instead of hiding the entire app on initial render, just wrap the return values of hooks.

Could you give an example of wrapping return value of a hook? For example if I'm using:

const { address } = useAccount()
const { data } = useContractRead({
    address: CONTRACT_ADDRESS,
    abi: CONTRACT_ABI,
    functionName: "getBalance",
    args: [address],
    enabled: isAuthenticated && Boolean(address),
  });

@llllvvuu
Copy link
Contributor

Following the updated example nextjs app https://github.com/wagmi-dev/wagmi/blob/main/examples/_dev/src/pages/index.tsx, this would cause a brief flash of a blank page when navigating between routes. Does anyone have a workaround/fix for this that doesn't cause the flash?

You need to make the workaround more precise. For example, instead of hiding the entire app on initial render, just wrap the return values of hooks.

Could you give an example of wrapping return value of a hook? For example if I'm using:

const { address } = useAccount()
const { data } = useContractRead({
    address: CONTRACT_ADDRESS,
    abi: CONTRACT_ABI,
    functionName: "getBalance",
    args: [address],
    enabled: isAuthenticated && Boolean(address),
  });
const _address = useAccount().address
const address = isMounted ? _address : undefined
const { data } = useContractRead({
    address: isMounted ? CONTRACT_ADDRESS : undefined,
    abi: CONTRACT_ABI,
    functionName: "getBalance",
    args: [address],
    enabled: isAuthenticated && Boolean(address),
  });

@0xdef1cafe
Copy link

I think it's okay to modify it like this.

import { useEffect } from 'react';
import { createClient } from 'wagmi';

const client = createClient({
  autoConnect: false,
  connectors,
  provider
})

export default function App({ Component, pageProps }: AppPropsWithLayout) {
  useEffect(() => {
    client.autoConnect();
  }, [])

  return (
    ...
  )
}

this is nasty and i love it

@cruzdanilo
Copy link
Contributor

cruzdanilo commented Sep 13, 2023

next.js app router is stable (out of beta) since v13.4, which was released on may 4th, 2023:
https://nextjs.org/blog/next-13-4

@MidnightLightning
Copy link

MidnightLightning commented Sep 17, 2023

While the overall issue is "rendering a component that uses web storage to change its appearance fails hydration errors" which is not wagmi-specific, the part of wagmi that is causing the issue (caching data) I think could be extended to allow a new option for this situation:

There's already a cacheTime parameter for most wagmi hooks, but the documentation says it defaults to zero. So I presume the internal logic is that on initial call, it serves any stored data it has, then because the stored data is stale (past zero milliseconds old) fetches new, and replaces the stored data with the new value (possibly the same value, just new cache time). However I'm not seeing the status nor isLoading/isFetching values update after page load...?

A solution to this scenario could be a new parameter to specify always render with null data once. Namely, if that parameter is set, the first time the hook is called, return data of null, with a status of loading. Then look up to see if there's cached data. If there is, update data to it (and status of success), triggering a second rendering of the component.

Most wagmi hooks have enabled input parameters, and specify refetch return values that I think could be used for another workaround style, but I don't see documentation on them (or what the difference is between isLoading and isFetching, and between isFetched and isFetchedAfterMount); updated documentation and examples on those could possibly help developers code appropriately for this situation using those tools.

Providing additional documentation on caching I pulled out into a separate request: #3017

@llllvvuu
Copy link
Contributor

FWIW upgrading react-query from v3 to v4 fixed hydration errors from there, so wagmis hydration errors shouldn't be a react-query issue

@abdullahmehboob20s
Copy link

abdullahmehboob20s commented Oct 19, 2023

I am aslo having this issue. But I am getting this error "Entire page /affiliates deopted into client-side rendering".

I have a provider wrapper around my App which looks like this.

"use client";

import { WagmiConfig } from "wagmi";
import { config, projectId, chains } from "utils/wagmiConfig";
import { createWeb3Modal } from "@web3modal/wagmi/react";
import { AppProgressBar } from "next-nprogress-bar";

createWeb3Modal({
  wagmiConfig: config,
  projectId,
  chains,
});

export default function Web3ModalProvider({ children }) {
  return (
    <WagmiConfig config={config}>
      {children}

      <AppProgressBar
        height="3px"
        color="#7e20fc"
        options={{ showSpinner: false }}
        shallowRouting
      />
    </WagmiConfig>
  );
}

which then wraps my whole app in layout.js which looks like this.

  <body>
        <Web3ModalProvider>
          <Layout>{children}</Layout>
        </Web3ModalProvider>
      </body>

If I am commenting <Web3ModalProvider> then warning is not showing.

Copy link
Contributor

github-actions bot commented Jan 6, 2024

This issue has been locked since it has been closed for more than 14 days.

If you found a concrete bug or regression related to it, please open a new bug report with a reproduction against the latest wagmi version. If you have any other comments you can create a new discussion.

@github-actions github-actions bot locked and limited conversation to collaborators Jan 6, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.