Web3 वॉलेट कनेक्शन के साथ NextJS 13 में हाइड्रेशन एरर को समझना
समस्या
यदि आपने हाल ही में अपने नए बीटा नेक्स्टजेएस एप्लिकेशन के साथ WAGMI स्थापित किया है और कुछ बुनियादी वॉलेट कनेक्शन करने की कोशिश की है, तो हो सकता है कि आपको एक त्रुटि का सामना करना पड़े जो हाइड्रेशन त्रुटियों को दिखाती है। यह ध्यान दिया जाना चाहिए कि यह पूरी तरह से नेक्स्टजेएस 13 का मुद्दा नहीं है, लेकिन हम इसे कवर करेंगे कि कैसे नेक्स्टजेएस 13 इसे अलग करने और हल करने में मदद करने के लिए कुछ सम्मेलनों को बदलता है।
हाइड्रेशन क्या है?
हाइड्रेशन क्लाइंट-साइड जावास्क्रिप्ट का उपयोग करने की प्रक्रिया है जो सर्वर-रेंडर किए गए HTML में एप्लिकेशन स्टेट और इंटरएक्टिविटी जोड़ने के लिए है। यह रिएक्ट की एक विशेषता है, जो गैट्सबी ढांचे को बनाने वाले अंतर्निहित उपकरणों में से एक है। Gatsby हाइड्रेशन का उपयोग बिल्ड समय पर बनाए गए स्थिर HTML को रिएक्ट एप्लिकेशन में बदलने के लिए करता है।
- रिएक्ट हाइड्रेशन को समझना
क्या हो रहा है?
समस्या यह है कि जब हम नेक्स्टजेएस जैसे एसएसआर (सर्वर-साइड रेंडरेड) रिएक्ट फ्रेमवर्क का उपयोग कर रहे हैं, तो यह तकनीकी रूप से पृष्ठ को एक विशिष्ट तरीके से प्रस्तुत करता है, और फिर जब क्लाइंट (ब्राउज़र) चीजों को प्रस्तुत करता है, तो यह अपेक्षा करता है कि राज्य प्रदान किया गया सर्वर द्वारा यह सुनिश्चित करने के लिए क्लाइंट की ओर से मेल खाता है कि यह जानता है कि इसकी स्थिति को कैसे प्रबंधित किया जाए।
यदि सर्वर-साइड स्टेट और क्लाइंट स्टेट मेल नहीं खाते हैं, तो आपको हाइड्रेशन एरर मिलता है।
इसे देखने का एक शानदार तरीका यह है कि यदि आप अपने ब्राउज़र पर JavaScrip को अक्षम करते हैं और दो DOM के बीच अंतर देखते हैं।
यदि आप हाइड्रेशन के बारे में अधिक जानना चाहते हैं, तो मैं निश्चित रूप से रिहाइड्रेशन के खतरों पर जोश कोमू द्वारा इस ब्लॉग पोस्ट को देखने की सलाह देता हूं ।
समाधान क्या है?
समाधान यह है कि हमें इस बारे में विभाजित करने की आवश्यकता है कि सर्वर द्वारा क्या संभाला जाना चाहिए और क्लाइंट पक्ष पर क्या संभाला जाना चाहिए। नेक्स्टजेएस 13 में कुछ नए समायोजन के साथ, कुछ दस्तावेज़ों में यह सर्वर और क्लाइंट के बीच फ़ाइलों का स्पष्ट पृथक्करण दिखाता है । हालाँकि यह केवल एक विचार है, यह कुछ ऐसा है जिसे हम शो में प्रदर्शित कर सकते हैं कि समाधान को कैसे हल किया जाए।
आवश्यकताएं
शुरू करने से पहले, सुनिश्चित करें कि अगले चरणों का पालन करने के लिए आपके कंप्यूटर पर निम्नलिखित स्थापित हैं।
- NVM या नोड v18.12.1
- पीएनपीएम v7.15.0
हम उस समस्या को फिर से बनाने जा रहे हैं जो ऊपर दिखाई गई थी, और फिर इसे ठीक करने के कुछ संभावित समाधानों पर पूर्वाभ्यास करेंगे।
आइए त्रुटि को पुन: उत्पन्न करने के लिए अपना प्रारंभिक सेटअप प्राप्त करें।
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;
हम अपने नेक्स्टजेएस ऐप को कॉन्फ़िगर करने के लिए कुछ नए बीटा नेक्स्टजेएस 13 डॉक्स का उपयोग करेंगे।
फ़ाइल: ./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>
)
};
फ़ाइल: ./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>
);
};
टेलविंड कॉन्फ़िगरेशन (वैकल्पिक)
यह अगला चरण वैकल्पिक है, लेकिन मुझे अच्छा लगता है जब यूआई को प्रदर्शित करते समय चीजें बेहतर दिखती हैं, और इसके लिए हम टेलविंड का उपयोग करेंगे ।
# FROM ./next13-wagmi-hydration
pnpm add -D tailwindcss postcss autoprefixer;
pnpx tailwindcss init -p;
फ़ाइल: ./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: [],
}
फ़ाइल: ./next13-wagmi-hyration/styles/global.css
@tailwind base;
@tailwind components;
@tailwind utilities;
फ़ाइल: ./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>
);
};
WAGMI कॉन्फ़िगरेशन
आगे आइए वॉलेट इंटरैक्शन की अनुमति देने के लिए WAGMI सेटअप प्राप्त करें।
# 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;
फ़ाइल: ./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;
फ़ाइल: ./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;
फ़ाइल: ./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>
)
};
फ़ाइल: ./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 माइग्रेटिंग पेज पर उनके बीटा डॉक्स देखें ।
इसे ठीक करने के लिए, हमें यह स्पष्ट करने की आवश्यकता है कि क्लाइंट के लिए प्रत्येक फ़ाइल के शीर्ष पर एक टिप्पणी का उपयोग करके क्लाइंट के लिए दो फाइलों को कैसे संभाला जाए use client;
।
हमें इसकी दो जगह जरूरत है। पहला हमारा है क्योंकि हम जानते हैं कि ज्यादातर प्रदाता हुक जैसे और जो ज्यादातर ग्राहक पक्ष में उपयोग किए जाते हैं provider.tsx
, का लाभ उठा रहे होंगे । दूसरा स्थान हमारा है , लेकिन यह अस्थायी होगा और समझाएं कि क्यों।useState
useEffect
page.tsx
फ़ाइल: ./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>
);
};
अब अगर हम कोशिश करते हैं और साइट को कनेक्ट करते हैं, तो भी हमें कोई समस्या नहीं दिखनी चाहिए।
अंत में, पेज को रिफ्रेश करते हैं और उस हाइड्रेशन एरर को देखते हैं।
महान! अब हमारे पास समस्या है, चलो समाधान पर चलते हैं।
समाधान
मैं चीजों को थोड़ा बेहतर तरीके से व्यवस्थित करने के बारे में कुछ विचारों के माध्यम से चलने जा रहा हूं और अनुकूलित विधि के साथ कुछ समाधान भी दिखाऊंगा।
सबसे पहले, आइए अलग करें कि हमारे क्लाइंट के लिए क्या आवश्यक है और हमारे सर्वर के लिए क्या आवश्यक है। यदि हम देखें तो page.tsx
हम देखेंगे कि यह केवल isConnected
वेरिएबल पर है कि हमें वास्तव में क्लाइंट द्वारा नियंत्रित की जाने वाली चीजों की आवश्यकता है। बाकी सब कुछ सर्वर द्वारा प्रदान किया जा सकता है।
आइए वॉलेट इंटरेक्शन को उसके स्वयं के घटक page.tsx
से हटाने use client
और अमूर्त करने के लिए रिफ्लेक्टर करें।
फ़ाइल: ./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>
);
};
अब जावास्क्रिप्ट को वापस सक्षम करते हैं और अपना नया ConnectWallet
घटक बनाते हैं।
# FROM ./next13-wagmi-hydration
touch ./app/wallet.tsx
फ़ाइल: ./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>
);
};
समाधान क्रेडिट
यह ध्यान दिया जाना चाहिए कि समाधान मूल रूप से जोश कोमू द्वारा अपने ब्लॉग पर द पेरिल्स ऑफ रिहाइड्रेशन पर बनाए गए हैं । जोश को बहुत-बहुत धन्यवाद।
पहला उपाय
आइए पहले समाधान पर काम करते हैं, जहां हम जांच करेंगे कि क्या घटक को पहले माउंट किया गया है, और यदि यह माउंट नहीं किया गया है, तो घटक को लोड न करें।
इसका ट्रैक रखने के लिए आप useState
साथ में कर सकते हैं useEffect
। useRef
यदि घटक माउंट किया गया है तो दुर्भाग्य से आप ट्रैक रखने के लिए उपयोग नहीं कर सकते ।
फ़ाइल: ./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>
);
};
अनुकूलित समाधान
हालांकि यह hasMounted
प्रत्येक घटक में जोड़ने के लिए थोड़ा दोहराव वाला हो जाता है, इसलिए हम उस कार्यक्षमता को अपने घटक में समाहित करके इसे एक कदम आगे ले जा सकते हैं।
# 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>
);
};
फ़ाइल: ./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
काम कर रहे पूर्ण कोड को देखने के लिए, इस गीथूब रिपॉजिटरी को देखें।
आगे क्या होगा?
नेक्स्टजेएस के साथ काम कर रहे साइन-इन विथ एथेरियम के शुरुआती कार्यान्वयन पर एक अन्य लेख जल्द ही आने वाला है।
यदि आपको इससे कुछ लाभ हुआ है, तो कृपया मुझे ट्विटर पर भी फॉलो करें (जहां मैं काफी सक्रिय हूं) @codingwithmanny और instagram @codingwithmanny पर ।