Memahami Kesalahan Hidrasi Di NextJS 13 Dengan Koneksi Web3 Wallet

Nov 25 2022
Cara Memperbaiki Kesalahan Hidrasi Di NextJS 13 Dengan Koneksi Dompet WAGMI Frontend
Masalah Jika Anda baru saja menginstal WAGMI dengan aplikasi NextJS beta Anda yang baru dan mencoba melakukan beberapa koneksi dompet dasar, Anda mungkin menemukan kesalahan yang menunjukkan kesalahan Hidrasi. Perlu dicatat bahwa ini bukan semata-mata masalah NextJS 13 tetapi kami akan membahas bagaimana NextJS 13 mengubah beberapa konvensi untuk membantu mengisolasi dan menyelesaikannya.
BerikutnyaJS 13 Kesalahan Hidrasi

Masalah

Jika Anda baru saja menginstal WAGMI dengan aplikasi NextJS beta yang baru dan mencoba melakukan beberapa koneksi dompet dasar, Anda mungkin menemukan kesalahan yang menunjukkan kesalahan Hidrasi. Perlu dicatat bahwa ini bukan semata-mata masalah NextJS 13 tetapi kami akan membahas bagaimana NextJS 13 mengubah beberapa konvensi untuk membantu mengisolasi dan menyelesaikannya.

Apa itu Hidrasi?

Hidrasi adalah proses menggunakan JavaScript sisi klien untuk menambahkan status aplikasi dan interaktivitas ke HTML yang dirender oleh server. Ini adalah fitur React, salah satu alat dasar yang membuat framework Gatsby. Gatsby menggunakan hidrasi untuk mengubah HTML statis yang dibuat saat pembuatan menjadi aplikasi React.
- Memahami React Hydration

Apa yang sedang terjadi?

Masalahnya adalah ketika kita menggunakan SSR (Server-Side Rendered) React Frameworks seperti NextJS, secara teknis merender halaman dengan cara tertentu, dan kemudian ketika klien (browser) merender sesuatu, ia mengharapkan status dirender oleh server cocok dengan apa yang ada di sisi klien untuk memastikannya mengetahui cara mengelola statusnya.

Jika status sisi server dan status klien tidak cocok, Anda akan mendapatkan kesalahan hidrasi.

Cara terbaik untuk melihatnya adalah jika Anda menonaktifkan JavaScrip di browser Anda dan melihat perbedaan antara kedua DOM tersebut.

Kiri: JavaScript Dinonaktifkan — Kanan: JavaScript Diaktifkan

Jika Anda ingin mempelajari lebih lanjut tentang hidrasi, saya merekomendasikan untuk melihat posting blog ini oleh Josh Comeau di The Perils of Rehydration .

Apa Solusinya?

Solusinya adalah kita perlu memecah belah tentang apa yang harus ditangani oleh server dan apa yang harus ditangani di sisi klien. Dengan beberapa penyesuaian baru pada NextJS 13, dalam beberapa dokumentasi ini menunjukkan pemisahan file yang jelas antara server dan klien . Meskipun itu hanya sebuah ide, itu adalah sesuatu yang dapat kami tunjukkan untuk menunjukkan bagaimana memecahkan solusinya.

Persyaratan

Sebelum kita mulai, pastikan untuk menginstal yang berikut di komputer Anda untuk mengikuti langkah selanjutnya.

  • NVM atau node v18.12.1
  • pnpm v7.15.0

Kami akan membuat ulang masalah yang ditampilkan di atas, lalu menelusuri beberapa kemungkinan solusi tentang cara memperbaikinya.

BerikutnyaJS 13 Kesalahan Hidrasi

Mari dapatkan pengaturan awal untuk mereproduksi kesalahan.

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;

Kami akan menggunakan beberapa Dokumen Beta NextJS 13 yang lebih baru untuk mengonfigurasi aplikasi NextJS kami.

Mengajukan: ./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>
  )
};

Mengajukan: ./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 UI Sederhana

Konfigurasi Tailwind (Opsional)

Langkah selanjutnya ini bersifat opsional, tetapi saya suka jika semuanya terlihat lebih baik saat mendemonstrasikan UI, dan untuk ini kita akan menggunakan Tailwind .

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

Mengajukan: ./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: [],
}

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

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

Mengajukan: ./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>
    );
};

NextJS dengan Tailwind

Konfigurasi WAGMI

Selanjutnya, mari siapkan WAGMI untuk memungkinkan interaksi dompet.

# 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;

Mengajukan: ./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;

Mengajukan: ./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;

Mengajukan: ./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>
  )
};

Mengajukan: ./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').

Lihat dokumen beta mereka di NextJS Migrating Pages .

Untuk memperbaikinya, kita perlu menjelaskan bagaimana dua file ditangani untuk klien dengan menggunakan komentar di bagian atas setiap file dengan use client;.

Ada dua tempat yang kita butuhkan ini. Yang pertama adalah kami provider.tsxkarena kami tahu bahwa sebagian besar penyedia akan memanfaatkan pengait seperti useStatedan useEffectyang sebagian besar digunakan di sisi klien. Tempat kedua adalah our page.tsx, tetapi ini bersifat sementara dan jelaskan alasannya.

Mengajukan: ./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>
    );
};

BerikutnyaJS Wallet Connection — Tidak Terhubung

Sekarang jika kami mencoba dan menghubungkan situs tersebut, kami masih tidak akan melihat masalah.

BerikutnyaJS Wallet Connection — Terhubung

Terakhir, mari segarkan halaman dan lihat kesalahan hidrasi itu.

BerikutnyaKoneksi Dompet JS — Kesalahan Hidrasi

Besar! Sekarang kita memiliki masalahnya, mari beralih ke solusinya.

Solusinya

Saya akan membahas beberapa ide tentang cara mengatur hal-hal sedikit lebih baik dan juga menunjukkan beberapa solusi dengan metode yang dioptimalkan.

Pertama, mari kita pisahkan apa yang dibutuhkan untuk klien kita dan apa yang dibutuhkan untuk server kita. Jika kita melihat page.tsxkita akan melihat bahwa hanya pada isConnectedvariabel kita benar-benar membutuhkan sesuatu untuk ditangani oleh klien. Segala sesuatu yang lain dapat diberikan oleh server.

Mari refactor page.tsxuntuk menghapus use clientdan mengabstraksi interaksi dompet ke komponennya sendiri.

Mengajukan: ./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>
    );
};

BerikutnyaKoneksi Dompet JS — JavaScript Dinonaktifkan

Sekarang mari aktifkan kembali JavaScript dan buat ConnectWalletkomponen baru kita.

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

Mengajukan: ./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>
    );
};

BerikutnyaKoneksi Dompet JS — Kesalahan Hidrasi

Kredit Solusi

Perlu dicatat bahwa solusi asli dibuat oleh Josh Comeau di blognya di The Perils of Rehydration . Banyak terima kasih kepada Josh.

Solusi Pertama

Mari kita kerjakan solusi pertama, di mana kita akan memeriksa apakah komponen sudah terpasang terlebih dahulu, dan jika belum terpasang, maka komponen tidak dimuat.

Untuk melacak ini, Anda dapat useStatebersama dengan useEffect. Sayangnya Anda tidak dapat menggunakan useRefuntuk melacak jika komponen sudah terpasang.

Mengajukan: ./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>
    );
};

BerikutnyaKoneksi Dompet JS — Solusi 1 Kesalahan Hidrasi Diperbaiki

Solusi yang Dioptimalkan

Itu menjadi sedikit berulang untuk ditambahkan hasMountedke setiap komponen, jadi kita bisa melangkah lebih jauh dengan mengabstraksi fungsionalitas itu ke dalam komponennya sendiri.

# 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>
    );
};

Mengajukan: ./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

Untuk melihat kode lengkap berfungsi, lihat repositori github ini.

Apa berikutnya?

Nantikan artikel lain tentang implementasi awal Sign-In With Ethereum bekerja dengan NextJS segera hadir.

Jika Anda mendapat nilai dari ini, ikuti juga saya di twitter (di mana saya cukup aktif) @codingwithmanny dan instagram di @codingwithmanny .