Skip to content

Commit

Permalink
✨ add useIsMounted hook and remove unused function
Browse files Browse the repository at this point in the history
  • Loading branch information
JohnsonMao committed Nov 19, 2023
1 parent aa4bd58 commit 53b2493
Show file tree
Hide file tree
Showing 10 changed files with 43 additions and 158 deletions.
2 changes: 1 addition & 1 deletion src/app/[lang]/Footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ type FooterProps = {

function Footer({ copyright }: FooterProps) {
return (
<Container as="footer" className="flex justify-center gap-3 ">
<Container as="footer" className="flex justify-center gap-3 py-4 ">
<Link
rel="license"
href="https://creativecommons.org/licenses/by-nc-sa/4.0/"
Expand Down
18 changes: 9 additions & 9 deletions src/app/[lang]/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,18 @@ import Container from '@/components/Container';
import Image from '@/components/Image';
import Link from '@/components/Link';
import useScroll, { ScrollHandler } from '@/hooks/useScroll';
import useRafState from '@/hooks/useRafState';
import cn from '@/utils/cn';
import { toFixedNumber } from '@/utils/math';
import useIsMounted from '@/hooks/useIsMounted';

type HeaderProps = {
avatar: React.ReactNode;
scrollThreshold?: number;
} & React.PropsWithChildren;

function Header({ avatar, children, scrollThreshold = 100 }: HeaderProps) {
const [avatarScale, setAvatarScale] = useRafState(0);
const isMounted = useIsMounted();
const [avatarScale, setAvatarScale] = useState(0);
const [headerFixed, setHeaderFixed] = useState(true);
const [headerTranslateY, setHeaderTranslateY] = useState(0);
const [willChange, setWillChange] = useState(true);
Expand Down Expand Up @@ -55,12 +56,10 @@ function Header({ avatar, children, scrollThreshold = 100 }: HeaderProps) {
if (scrollY > scrollThreshold) {
setAvatarScale(1);
} else {
setAvatarScale(
toFixedNumber(2)(1.5 - scrollY / (scrollThreshold * 2))
);
setAvatarScale(toFixedNumber(2)(1.5 - scrollY / (scrollThreshold * 2)));
}
},
[scrollThreshold, setAvatarScale]
[scrollThreshold]
);

const scrollHandler = useCallback<ScrollHandler>(
Expand Down Expand Up @@ -94,8 +93,9 @@ function Header({ avatar, children, scrollThreshold = 100 }: HeaderProps) {
<Container
as="header"
className={cn(
'sticky top-auto z-10 translate-y-[var(--header-translate-y)]',
headerFixed && 'top-[var(--scroll-threshold)] translate-y-0'
'sticky top-auto z-10 translate-y-[var(--header-translate-y)] opacity-0 transition-opacity duration-500 ease-in-out',
headerFixed && 'top-[var(--scroll-threshold)] translate-y-0',
isMounted && 'opacity-100'
)}
style={headerStyles}
>
Expand All @@ -105,7 +105,7 @@ function Header({ avatar, children, scrollThreshold = 100 }: HeaderProps) {
<div className="py-7">
<div
className={cn(
'relative z-10 inline-block origin-left scale-[var(--avatar-scale)] rounded-full',
'relative z-10 inline-block origin-bottom-left scale-[var(--avatar-scale)] rounded-full',
willChange && 'will-change-transform'
)}
>
Expand Down
12 changes: 4 additions & 8 deletions src/components/ThemeSwitcher/ThemeSwitcher.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,22 @@
'use client';

import { useState, useEffect } from 'react';
import { useTheme } from 'next-themes';
import { BsSunFill, BsMoonFill } from 'react-icons/bs';
import useIsMounted from '@/hooks/useIsMounted';

type ThemeSwitcher = {
type ThemeSwitcherProps = {
className?: string;
};

function ThemeSwitcher({ className }: ThemeSwitcher) {
const [isMounted, setIsMounted] = useState(false);
function ThemeSwitcher({ className }: ThemeSwitcherProps) {
const isMounted = useIsMounted();
const { resolvedTheme, setTheme } = useTheme();
const isDarkTheme = resolvedTheme === 'dark';

const handleClick = () => {
setTheme(isDarkTheme ? 'light' : 'dark');
};

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

if (!isMounted) {
return (
<div className={className}>
Expand Down
4 changes: 2 additions & 2 deletions src/components/ThemeSwitcher/index.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
import ThemeSwitcher from './ThemeSwitcher';
import ThemeSwitcherProps from './ThemeSwitcher';

export default ThemeSwitcher;
export default ThemeSwitcherProps;
10 changes: 10 additions & 0 deletions src/hooks/__tests__/useMounted.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { renderHook } from '@testing-library/react';
import useIsMounted from '../useIsMounted';

describe('useIsMounted hook', () => {
it('should return true if component is mounted', () => {
const { result } = renderHook(() => useIsMounted());

expect(result.current).toBeTruthy();
});
});
38 changes: 0 additions & 38 deletions src/hooks/__tests__/useRafState.test.ts

This file was deleted.

16 changes: 16 additions & 0 deletions src/hooks/useIsMounted.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { useEffect, useState } from 'react';

/**
* This hook to track whether the component is mounted.
*/
function useIsMounted() {
const [isMounted, setIsMounted] = useState(false);

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

return isMounted;
}

export default useIsMounted;
21 changes: 0 additions & 21 deletions src/hooks/useRafState.ts

This file was deleted.

41 changes: 1 addition & 40 deletions src/utils/__tests__/math.test.ts
Original file line number Diff line number Diff line change
@@ -1,45 +1,6 @@
import { clamp, pipe, toFixedNumber } from '../math';
import { toFixedNumber } from '../math';

describe('math function', () => {
it.each<[number, number, number, number]>([
[15, 15, 20, 0],
[-5, -5, -20, 0],
[0, -5, 20, 0],
[0, 15, -20, 0],
[20, 50, 20, 0],
[-20, -50, -20, 0],
[-10, 0, -20, -10],
[-10, 0, -10, -20],
[10, 0, 10, 20],
[10, 0, 20, 10],
[5, 5, 20, -20],
])(
'%#. should correctly clamp a value (%o) within the specified range',
(expected, input, ...params) => {
expect(clamp(...params)(input)).toBe(expected);
}
);

it('should process an input value through a series of functions in sequence', () => {
expect(pipe(1)).toBe(1);
expect(pipe(2, (n) => n * 2)).toBe(4);
expect(
pipe(
3,
(n) => n * 3,
(n) => n + 2
)
).toBe(11);
expect(
pipe(
4,
(n) => n * 2,
(n) => n / 3,
(n) => n.toFixed(2)
)
).toBe('2.67');
});

it('should round a number to the specified number of decimal places', () => {
const input = 1.3456;
expect(toFixedNumber(0)(input)).toBe(1);
Expand Down
39 changes: 0 additions & 39 deletions src/utils/math.ts
Original file line number Diff line number Diff line change
@@ -1,42 +1,3 @@
export type UnaryFunction<A, B> = (a: A) => B;

/** Processes an input value through a series of functions in sequence. */
export function pipe<T>(input: T): T;
export function pipe<T, A>(input: T, fn1: UnaryFunction<T, A>): A;
export function pipe<T, A, B>(
input: T,
fn1: UnaryFunction<T, A>,
fn2: UnaryFunction<A, B>
): B;
export function pipe<T, A, B, C>(
input: T,
fn1: UnaryFunction<T, A>,
fn2: UnaryFunction<A, B>,
fn3: UnaryFunction<B, C>
): C;
export function pipe<T, A, B, C, D>(
input: T,
fn1: UnaryFunction<T, A>,
fn2: UnaryFunction<A, B>,
fn3: UnaryFunction<B, C>,
fn4: UnaryFunction<C, D>
): D;

export function pipe(
input: unknown,
...functions: UnaryFunction<unknown, unknown>[]
) {
return functions.reduce((value, fn) => fn(value), input);
}

/** Restricts the given value within a specified range. */
export const clamp = (a: number, b: number) => (value: number) => {
const min = Math.min(a, b);
const max = Math.max(a, b);

return Math.min(Math.max(value, min), max);
};

/** Round a number to a specified number of decimal places. */
export const toFixedNumber = (digits: number) => (value: number) => {
const pow = Math.pow(10, digits);
Expand Down

0 comments on commit 53b2493

Please sign in to comment.