Bir Web3 Cüzdan Bağlantısıyla NextJS 13'te Hidrasyon Hatalarını Anlama

Nov 25 2022
Bir Ön Uç WAGMI Cüzdan Bağlantısı ile NextJS 13'te Hidrasyon Hatalarını Düzeltme
Sorun WAGMI'yi yeni beta NextJS uygulamanızla yakın zamanda yüklediyseniz ve bazı temel cüzdan bağlantılarını yapmaya çalıştıysanız, Hidrasyon hatalarını gösteren bir hatayla karşılaşmış olabilirsiniz. Bunun yalnızca bir NextJS 13 sorunu olmadığı unutulmamalıdır, ancak bunu izole etmeye ve çözmeye yardımcı olmak için NextJS 13'ün bazı kuralları nasıl değiştirdiğini ele alacağız.
NextJS 13 Hidrasyon Hatası

Sorun

Yakın zamanda yeni beta NextJS uygulamanızla WAGMI'yi yüklediyseniz ve bazı temel cüzdan bağlantılarını yapmaya çalıştıysanız Hidrasyon hatalarını gösteren bir hatayla karşılaşmış olabilirsiniz. Bunun yalnızca bir NextJS 13 sorunu olmadığı unutulmamalıdır, ancak bunu izole etmeye ve çözmeye yardımcı olmak için NextJS 13'ün bazı kuralları nasıl değiştirdiğini ele alacağız.

Hidrasyon Nedir?

Hidrasyon, sunucu tarafından oluşturulan HTML'ye uygulama durumu ve etkileşim eklemek için istemci tarafı JavaScript'i kullanma işlemidir. Bu, Gatsby çerçevesini oluşturan temel araçlardan biri olan React'in bir özelliğidir. Gatsby, derleme zamanında oluşturulan statik HTML'yi bir React uygulamasına dönüştürmek için hidrasyon kullanır.
- React Hydration'ı Anlamak

Ne oluyor?

Sorun şu ki, NextJS gibi SSR (Sunucu Tarafında Oluşturulan) React Frameworks kullandığımızda, teknik olarak sayfayı belirli bir şekilde oluşturur ve ardından müşteri (tarayıcı) bir şeyler oluşturduğunda, durumun olmasını bekler. durumunu nasıl yöneteceğini bildiğinden emin olmak için istemci tarafında olanlarla eşleşir.

Sunucu tarafı durumu ile istemci durumu eşleşmiyorsa, bir hidrasyon hatası alırsınız.

Bunu görmenin harika bir yolu, tarayıcınızda JavaScrip'i devre dışı bırakmanız ve iki DOM arasındaki farkı görmenizdir.

Sol: JavaScript Devre Dışı — Sağ: JavaScript Etkin

Hidrasyon hakkında daha fazla bilgi edinmek istiyorsanız, kesinlikle Josh Comeau'nun Rehidrasyonun Tehlikeleri hakkındaki bu blog gönderisine bir göz atmanızı tavsiye ederim .

Çözüm Nedir?

Çözüm, nelerin sunucu tarafından ele alınması gerektiği ve nelerin istemci tarafında ele alınması gerektiği konusunda bölünmemiz gerektiğidir. NextJS 13'teki bazı yeni ayarlamalarla, bazı belgelerde sunucu ve istemci arasında dosyaların net bir şekilde ayrıldığını gösterir . Bu sadece bir fikir olmasına rağmen, çözümün nasıl çözüleceğini göstermek için gösterebileceğimiz bir şeydir.

Gereksinimler

Başlamadan önce, sonraki adımları takip etmek için aşağıdakilerin bilgisayarınızda yüklü olduğundan emin olun.

  • NVM veya düğüm v18.12.1
  • pnpm v7.15.0

Yukarıda gösterilen sorunu yeniden oluşturacağız ve ardından nasıl düzeltileceğine dair bazı olası çözümler üzerinde yol göstereceğiz.

NextJS 13 Hidrasyon Hatası

Hatayı yeniden oluşturmak için ilk kurulumumuzu yapalım.

pnpm create next-app --typescript next13-wagmi-hydration;

# Expected Prompts:
#? Would you like to use ESLint with this project? › No / Yes
# Creating a new Next.js app in /path/to/next13-wagmi-hydration.
#
# Using pnpm.
#
# Installing dependencies:
# - react
# - react-dom
# - next
# - typescript
# - @types/react
# - @types/node
# - @types/react-dom
# - eslint
# - eslint-config-next

pnpm add wagmi ethers;

NextJS uygulamamızı yapılandırmak için daha yeni Beta NextJS 13 Docs'tan bazılarını kullanacağız.

Dosya: ./next13-wagmi-hydration/next.config.js

/** @type {import('next').NextConfig} */
const nextConfig = {
  reactStrictMode: true,
  swcMinify: true,
  experimental: {
    appDir: true,
  },
};

module.exports = nextConfig;

# FROM ./next13-wagmi-hydration
mkdir ./app;

# FROM ./next13-wagmi-hydration
mv ./pages/index.tsx app/page.tsx
rm -rf pages;

# FROM ./next13-wagmi-hydration
touch ./pages/layout.text

// Imports
// ========================================================
import '../styles/globals.css';

// Layout
// ========================================================
export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html>
      <head />
      <body>
          {children}
      </body>
    </html>
  )
};

Dosya: ./next13-wagmi-hyration/app/page.tsx

// Imports
// ========================================================
import React from 'react';

// Page
// ========================================================
export default function Home() {
    // Render
    return (
        <div>
            <h1 className="text-2xl text-white font-medium mb-6">Wallet Connection</h1>
        </div>
    );
};

http://localhost:300 Basit Kullanıcı Arayüzü

Tailwind Yapılandırması (İsteğe Bağlı)

Bu sonraki adım isteğe bağlıdır, ancak kullanıcı arayüzünün demosunu yaparken işlerin daha iyi görünmesini seviyorum ve bunun için Tailwind kullanacağız .

# FROM ./next13-wagmi-hydration
pnpm add -D tailwindcss postcss autoprefixer;
pnpx tailwindcss init -p;

Dosya: ./next13-wagmi-hyration/tailwind.config.js

/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [
    "./app/**/*.{js,ts,jsx,tsx}",
    "./components/**/*.{js,ts,jsx,tsx}",
  ],
  theme: {
    extend: {},
  },
  plugins: [],
}

Dosya: ./next13-wagmi-hyration/styles/global.css

@tailwind base;
@tailwind components;
@tailwind utilities;

Dosya: ./next13-wagmi-hyration/app/layout.tsx

// Imports
// ========================================================
import '../styles/globals.css';

// Layout
// ========================================================
export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html>
      <head />
      <body className="bg-zinc-900">
          {children}
      </body>
    </html>
  )
};

// Imports
// ========================================================
import React from 'react';

// Page
// ========================================================
export default function Home() {
    // Render
    return (
        <div className="p-8">
            <h1 className="text-2xl text-white font-medium mb-6">Wallet Connection</h1>
        </div>
    );
};

Tailwind ile NextJS

WAGMI Yapılandırması

Ardından, cüzdan etkileşimlerine izin vermek için WAGMI kurulumunu yapalım.

# FROM ./next13-wagmi-hydration
pnpm add wagmi ethers;

# FROM ./next13-wagmi-hydration
mkdir ./providers;
mkdir ./providers/wagmi;
touch ./providers/wagmi/index.tsx;
touch ./app/providers.tsx;

Dosya: ./next13-wagmi-hyration/providers/wagmi/index.tsx

// Imports
// ========================================================
import React from 'react';
import { WagmiConfig, createClient } from "wagmi";
import { getDefaultProvider } from 'ethers';

// Config
// ========================================================
const client = createClient({
    autoConnect: true,
    provider: getDefaultProvider()
});

// Provider
// ========================================================
const WagmiProvider = ({ children }: { children: React.ReactNode }) => {
    return <WagmiConfig client={client}>{children}</WagmiConfig>
};

// Exports
// ========================================================
export default WagmiProvider;

Dosya: ./next13-wagmi-hyration/app/providers.tsx

// Imports
// ========================================================
import React from 'react';
import WagmiProvider from "../providers/wagmi";

// Root Provider
// ========================================================
const RootProvider = ({ children }: { children: React.ReactNode }) => {
    return <div>
        <WagmiProvider>
            {children}
        </WagmiProvider>
    </div>
};

// Exports
// ========================================================
export default RootProvider;

Dosya: ./next13-wagmi-hyration/app/layout.tsx

// Imports
// ========================================================
import RootProvider from "./providers";
import '../styles/globals.css';

// Layout
// ========================================================
export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html>
      <head />
      <body className="bg-zinc-900">
          <RootProvider>
            {children}
          </RootProvider>
      </body>
    </html>
  )
};

Dosya: ./next13-wagmi-hyration/app/page.tsx

// Imports
// ========================================================
import React from 'react';
import { useAccount, useConnect, useDisconnect } from "wagmi";
import { InjectedConnector } from "wagmi/connectors/injected";

// Page
// ========================================================
export default function Home() {
    // State / Props
    const { address, isConnected } = useAccount();
    const { connect } = useConnect({
      connector: new InjectedConnector(),
    });
    const { disconnect } = useDisconnect()

    // Render
    return (
        <div className="p-8">
            <h1 className="text-2xl text-white font-medium mb-6">Wallet Connection</h1>

            {!isConnected
                ? <div>
                    <button className="h-10 bg-blue-600 text-white px-6 rounded-full hover:bg-blue-800 transition-colors ease-in-out duration-200" onClick={() => connect()}>Connect Wallet</button>
                </div>
                : <div>
                    <label className="text-zinc-400 block mb-2">Wallet Address Connected</label>
                    <code className="bg-zinc-700 text-zinc-200 p-4 rounded block mb-4"><pre>{address}</pre></code>
                    <button className="h-10 bg-red-600 text-white px-6 rounded-full hover:bg-red-800 transition-colors ease-in-out duration-200" onClick={() => disconnect()}>Disconnect Wallet</button>
                </div>}
        </div>
    );
};

# when running pnpm run dev
wait  - compiling...
error - ./node_modules/.pnpm/@[email protected]_fsy4krnncv4idvr4txy3aqiuqm/node_modules/@tanstack/react-query-persist-client/build/lib/PersistQueryClientProvider.mjs
Attempted import error: 'useState' is not exported from 'react' (imported as 'React').

NextJS Migrating Pages'daki beta belgelerine göz atın .

Bunu düzeltmek için, her dosyanın başında use client;.

Buna ihtiyacımız olan iki yer var. Birincisi , çoğu sağlayıcının çoğunlukla istemci tarafında kullanılan kancalardan provider.tsxyararlanacağını bildiğimiz için . İkinci sıra bizim , ama bu geçici olacak ve nedenini açıklayacak.useStateuseEffectpage.tsx

Dosya: ./next13-wagmi-hyration/app/providers.tsx

'use client';

// Imports
// ========================================================
import React from 'react';
import WagmiProvider from "../providers/wagmi";

// Root Provider
// ========================================================
const RootProvider = ({ children }: { children: React.ReactNode }) => {
    return <div>
        <WagmiProvider>
            {children}
        </WagmiProvider>
    </div>
};

// Exports
// ========================================================
export default RootProvider;

'use client';

// Imports
// ========================================================
import React from 'react';
import { useAccount, useConnect, useDisconnect } from "wagmi";
import { InjectedConnector } from "wagmi/connectors/injected";

// Page
// ========================================================
export default function Home() {
    // State / Props
    const { address, isConnected } = useAccount();
    const { connect } = useConnect({
      connector: new InjectedConnector(),
    });
    const { disconnect } = useDisconnect()

    // Render
    return (
        <div className="p-8">
            <h1 className="text-2xl text-white font-medium mb-6">Wallet Connection</h1>

            {!isConnected
                ? <div>
                    <button className="h-10 bg-blue-600 text-white px-6 rounded-full hover:bg-blue-800 transition-colors ease-in-out duration-200" onClick={() => connect()}>Connect Wallet</button>
                </div>
                : <div>
                    <label className="text-zinc-400 block mb-2">Wallet Address Connected</label>
                    <code className="bg-zinc-700 text-zinc-200 p-4 rounded block mb-4"><pre>{address}</pre></code>
                    <button className="h-10 bg-red-600 text-white px-6 rounded-full hover:bg-red-800 transition-colors ease-in-out duration-200" onClick={() => disconnect()}>Disconnect Wallet</button>
                </div>}
        </div>
    );
};

NextJS Cüzdan Bağlantısı — Bağlı Değil

Şimdi siteyi bağlamayı denersek, yine de bir sorun görmemeliyiz.

NextJS Cüzdan Bağlantısı — Bağlandı

Son olarak sayfayı yenileyelim ve hidrasyon hatasını görelim.

NextJS Cüzdan Bağlantısı — Hidrasyon Hatası

Harika! Şimdi sorunumuz var, çözüme geçelim.

Çözüm

Bazı şeyleri nasıl daha iyi organize edeceğimize dair bazı fikirlerin üzerinden geçeceğim ve ayrıca optimize edilmiş bir yöntemle birkaç çözüm göstereceğim.

Öncelikle clientimiz için gerekli olanlar ile serverimiz için gerekli olanları ayıralım. ' a bakarsak , müşteri tarafından ele alınması gereken şeylerin page.tsxyalnızca değişkende olduğunu fark ederiz. isConnectedGeri kalan her şey sunucu tarafından oluşturulabilir.

page.tsxM-cüzdan etkileşimini kaldırmak use clientve kendi bileşenine soyutlamak için yeniden düzenleme yapalım .

Dosya: ./next13-wagmi-hyration/app/page.tsx

// Imports
// ========================================================
import React from 'react';
import ConnectWallet from './wallet';

// Page
// ========================================================
export default function Home() {
    // Render
    return (
        <div className="p-8">
            <h1 className="text-2xl text-white font-medium mb-6">Wallet Connection</h1>

            <ConnectWallet />
        </div>
    );
};

NextJS Cüzdan Bağlantısı — JavaScript Devre Dışı

Şimdi JavaScript'i tekrar etkinleştirelim ve yeni ConnectWalletbileşenimizi oluşturalım.

# FROM ./next13-wagmi-hydration
touch ./app/wallet.tsx

Dosya: ./next13-wagmi-hyration/app/wallet.tsx

'use client';

// Imports
// ========================================================
import React from 'react';
import { useAccount, useConnect, useDisconnect } from "wagmi";
import { InjectedConnector } from "wagmi/connectors/injected";

// Page
// ========================================================
export default function Home() {
    // State / Props
    const { address, isConnected } = useAccount();
    const { connect } = useConnect({
      connector: new InjectedConnector(),
    });
    const { disconnect } = useDisconnect()

    // Render
    return (
        <div>
            {!isConnected
                ? <div>
                    <button className="h-10 bg-blue-600 text-white px-6 rounded-full hover:bg-blue-800 transition-colors ease-in-out duration-200" onClick={() => connect()}>Connect Wallet</button>
                </div>
                : <div>
                    <label className="text-zinc-400 block mb-2">Wallet Address Connected</label>
                    <code className="bg-zinc-700 text-zinc-200 p-4 rounded block mb-4"><pre>{address}</pre></code>
                    <button className="h-10 bg-red-600 text-white px-6 rounded-full hover:bg-red-800 transition-colors ease-in-out duration-200" onClick={() => disconnect()}>Connect Wallet</button>
                </div>}
        </div>
    );
};

NextJS Cüzdan Bağlantısı — Hidrasyon Hatası

Çözüm Kredisi

Çözümlerin orijinal olarak Josh Comeau tarafından The Perils of Rehydration hakkındaki blogunda yaratıldığı belirtilmelidir . Josh'a çok teşekkürler.

İlk Çözüm

Önce bileşenin monte edilip edilmediğini kontrol edeceğimiz ve monte edilmemişse bileşeni yüklemeyeceğimiz ilk çözüm üzerinde çalışalım.

Bunu takip etmek useStateiçin useEffect. useRefNe yazık ki bileşenin monte edilip edilmediğini takip etmek için kullanamazsınız .

Dosya: ./next13-wagmi-hyration/app/wallet.tsx

'use client';

// Imports
// ========================================================
import React, { useState, useEffect } from 'react';
import { useAccount, useConnect, useDisconnect } from "wagmi";
import { InjectedConnector } from "wagmi/connectors/injected";

// Page
// ========================================================
export default function Wallet() {
    // State / Props
    const [hasMounted, setHasMounted] = useState(false);
    const { address, isConnected } = useAccount();
    const { connect } = useConnect({
      connector: new InjectedConnector(),
    });
    const { disconnect } = useDisconnect()

    // Hooks
    useEffect(() => {
        setHasMounted(true);
    }, [])

    // Render
    if (!hasMounted) return null;

    return (
        <div>
            {!isConnected
                ? <div>
                    <button className="h-10 bg-blue-600 text-white px-6 rounded-full hover:bg-blue-800 transition-colors ease-in-out duration-200" onClick={() => connect()}>Connect Wallet</button>
                </div>
                : <div>
                    <label className="text-zinc-400 block mb-2">Wallet Address Connected</label>
                    <code className="bg-zinc-700 text-zinc-200 p-4 rounded block mb-4"><pre>{address}</pre></code>
                    <button className="h-10 bg-red-600 text-white px-6 rounded-full hover:bg-red-800 transition-colors ease-in-out duration-200" onClick={() => disconnect()}>Connect Wallet</button>
                </div>}
        </div>
    );
};

NextJS Cüzdan Bağlantısı — 1. Çözüm Hidrasyon Hatası Düzeltildi

Optimize Edilmiş Çözüm

Her bileşene eklemek biraz tekrarlı olsa da hasMounted, bu işlevselliği kendi bileşenine soyutlayarak bir adım daha ileri götürebiliriz.

# FROM ./next13-wagmi-hydration
touch ./app/clientOnly.tsx;

'use client';

// Imports
// ========================================================
import React, { useState, useEffect } from 'react';

// Page
// ========================================================
export default function ClientOnly({ children }: { children: React.ReactNode }) {
    // State / Props
    const [hasMounted, setHasMounted] = useState(false);

    // Hooks
    useEffect(() => {
        setHasMounted(true);
    }, [])

    // Render
    if (!hasMounted) return null;

    return (
        <div>
            {children}
        </div>
    );
};

Dosya: ./next13-wagmi-hyration/app/wallet.tsx

'use client';

// Imports
// ========================================================
import React from 'react';
import ClientOnly from './clientOnly';
import { useAccount, useConnect, useDisconnect } from "wagmi";
import { InjectedConnector } from "wagmi/connectors/injected";

// Page
// ========================================================
export default function Wallet() {
    // State / Props
    const { address, isConnected } = useAccount();
    const { connect } = useConnect({
      connector: new InjectedConnector(),
    });
    const { disconnect } = useDisconnect()

    // Render
    return (
        <div>
            <ClientOnly>
                {!isConnected
                    ? <div>
                        <button className="h-10 bg-blue-600 text-white px-6 rounded-full hover:bg-blue-800 transition-colors ease-in-out duration-200" onClick={() => connect()}>Connect Wallet</button>
                    </div>
                    : <div>
                        <label className="text-zinc-400 block mb-2">Wallet Address Connected</label>
                        <code className="bg-zinc-700 text-zinc-200 p-4 rounded block mb-4"><pre>{address}</pre></code>
                        <button className="h-10 bg-red-600 text-white px-6 rounded-full hover:bg-red-800 transition-colors ease-in-out duration-200" onClick={() => disconnect()}>Connect Wallet</button>
                    </div>}
            </ClientOnly>
        </div>
    );
};

      
                
NextJS Wallet Connection — Optimized Solution For Hydration Error Fixed

Kodun tamamının çalıştığını görmek için bu github deposuna göz atın.

Sıradaki ne?

Yakında gelecek olan NextJS ile çalışan Ethereum ile Oturum Açmanın ilk uygulaması hakkında başka bir makale bekleyin.

Bundan değer aldıysanız, lütfen beni twitter'da (oldukça aktif olduğum yer) @codingwithmanny'de ve @codingwithmanny'de instagramda takip edin .