Skip to content

Commit

Permalink
Merge pull request #473 from DeXter-on-Radix/fix-hydration-error
Browse files Browse the repository at this point in the history
Fix hydration error
  • Loading branch information
fliebenberg authored Jul 21, 2024
2 parents 571c3ec + 2e779ef commit f322353
Show file tree
Hide file tree
Showing 4 changed files with 70 additions and 12 deletions.
32 changes: 32 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -178,3 +178,35 @@ To add a new banner, follow these steps:
3. If you are the designer creating the banner, the content needs to be delivered as an SVG with a transparent background (see examples for [desktop](https://github.com/DeXter-on-Radix/website/blob/main/public/promo-banners/validator-node-staking/desktop-600x80.svg) or [mobile](https://github.com/DeXter-on-Radix/website/blob/main/public/promo-banners/validator-node-staking/mobile-600x200.svg)). Furthermore, ensure there is only a single call to action (CTA). Avoid having multiple competing actions like "STAKE NOW" and "learn more". Decide which one is more important and design the banner accordingly :D
4. Upload both files to `/public/promo-banners/`.
5. Fill out `imageUrl`, `imageUrlMobile` and optionally `redirecturl` inside [`src/app/layout.tsx`](https://github.com/DeXter-on-Radix/website/blob/main/src/app/layout.tsx#L15-L20).

## Hydration Error Handling

Problem: Since the radix connect button supports caching logged in users, components that depend on the users logg in status will have different initial renders based on whether the user is logged in or not. This is a disallowed react/nextJS pattern and throws a hydration error.

### General Rule: if the component's initial render depends on the users login status, use the following fix

```tsx
// Import custom hydration error hook
import { useHydrationErrorFix } from "hooks";

function SubmitButton() {
// SubmitButton's initial render depends on connection status! Hydratino Fix is required!
const { isConnected } = useAppSelector((state) => state.radix);

// Hydration fix goes at the top
const isClient = useHydrationErrorFix();

// Additional code like useEffect or other hooks go here!
// ...

// Ensures initial rendering is always empty and fixes hydration error.
// Add this line always before the return!
if (!isClient) return <></>;

return (
<button>
<p>{isConnectd ? "submit" : "please connect wallet to submit"}</p>
</button>
);
}
```
19 changes: 9 additions & 10 deletions src/app/components/OrderInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,12 @@ import {
truncateWithPrecision,
} from "../utils";

import { useAppDispatch, useAppSelector, useTranslations } from "hooks";
import {
useAppDispatch,
useAppSelector,
useTranslations,
useHydrationErrorFix,
} from "hooks";
import { fetchBalances } from "state/pairSelectorSlice";
import {
OrderSide,
Expand Down Expand Up @@ -367,6 +372,7 @@ function PostOnlyCheckbox() {
}

function SubmitButton() {
const isClient = useHydrationErrorFix(); // to fix HydrationError
const t = useTranslations();
const dispatch = useAppDispatch();
const { side, type, token1, quote, quoteDescription, quoteError } =
Expand All @@ -384,15 +390,8 @@ function SubmitButton() {
.replaceAll("<$SIDE>", t(side))
.replaceAll("<$TOKEN_SYMBOL>", token1.symbol);

// Fix hydration error:
// https://nextjs.org/docs/messages/react-hydration-error#solution-1-using-useeffect-to-run-on-the-client-only
const [isClient, setIsClient] = useState(false);
useEffect(() => {
setIsClient(true);
}, []);
if (!isClient) {
return <></>;
}
// Fix HydrationError
if (!isClient) return <></>;

return (
<button
Expand Down
12 changes: 12 additions & 0 deletions src/app/hooks.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useDispatch, useSelector } from "react-redux";
import type { TypedUseSelectorHook } from "react-redux";
import type { RootState, AppDispatch } from "./state/store";
import { useEffect, useState } from "react";

// https://redux-toolkit.js.org/tutorials/typescript#define-typed-hooks
export const useAppDispatch: () => AppDispatch = useDispatch;
Expand All @@ -18,3 +19,14 @@ export const useTranslations = () => {
};
return t;
};

// Hook to fix hydration errors by delaying rendering until client-side mount
export const useHydrationErrorFix = () => {
const [isClient, setIsClient] = useState(false);

useEffect(() => {
setIsClient(true);
}, []);

return isClient;
};
19 changes: 17 additions & 2 deletions src/app/rewards/page.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
"use client";

import { useEffect, useState } from "react";
import { useAppDispatch, useAppSelector, useTranslations } from "hooks";
import {
useAppDispatch,
useAppSelector,
useTranslations,
useHydrationErrorFix,
} from "hooks";
import {
fetchAddresses,
fetchReciepts,
Expand Down Expand Up @@ -91,6 +96,7 @@ function HeaderComponent() {
}

function RewardsCard() {
const isClient = useHydrationErrorFix(); // to fix HydrationError
const dispatch = useAppDispatch();
const { isConnected, walletData } = useAppSelector((state) => state.radix);
const account = walletData.accounts[0]?.address;
Expand Down Expand Up @@ -124,6 +130,10 @@ function RewardsCard() {
loadRewards();
}
}, [dispatch, isConnected, account, pairsList]);

// Fix HydrationError
if (!isClient) return <></>;

return (
<div className="max-w-[400px] sm:max-w-[600px] px-4 py-4 sm:px-12 sm:py-8 m-auto mt-2 sm:mt-14 mb-28 bg-[#191B1D] rounded-xl max-[450px]:mx-5">
<div className="flex flex-col">
Expand Down Expand Up @@ -200,13 +210,17 @@ function RewardsOverview() {
}

function ClaimButton() {
const isClient = useHydrationErrorFix(); // to fix HydrationError
const t = useTranslations();
const dispatch = useAppDispatch();
const { isConnected } = useAppSelector((state) => state.radix);
const { rewardData } = useAppSelector((state) => state.rewardSlice);
const userHasRewards = getUserHasRewards(rewardData);
const disabled = !isConnected || !userHasRewards;

// Fix HydrationError
if (!isClient) return <></>;

return (
<button
className={`w-full max-w-[220px] m-auto font-bold text-sm tracking-[.1px] min-h-[44px] p-3 my-6 uppercase rounded ${
Expand Down Expand Up @@ -243,6 +257,7 @@ function ClaimButton() {

function RewardsDetails() {
const [isOpen, setIsOpen] = useState(true);
const isClient = useHydrationErrorFix(); // to fix HydrationError
const { isConnected } = useAppSelector((state) => state.radix);
const { rewardData, tokensList } = useAppSelector(
(state) => state.rewardSlice
Expand All @@ -255,7 +270,7 @@ function RewardsDetails() {
}
}, [isConnected]);

if (!isConnected || !userHasRewards) {
if (!isConnected || !userHasRewards || !isClient) {
return <></>;
}

Expand Down

0 comments on commit f322353

Please sign in to comment.