Skip to content

Commit

Permalink
♻️ refactor hook and not found page
Browse files Browse the repository at this point in the history
  • Loading branch information
JohnsonMao committed Sep 26, 2023
1 parent 8c96151 commit 5dc724d
Show file tree
Hide file tree
Showing 9 changed files with 48 additions and 51 deletions.
2 changes: 0 additions & 2 deletions src/app/[lang]/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ import { Locale, getDictionary, locales } from '~/i18n';
import Header, { HeaderProps } from '@/components/Header';
import Footer from '@/components/Footer';

export const dynamic = 'force-static';

export async function generateStaticParams() {
return locales.map((lang) => ({ lang }));
}
Expand Down
2 changes: 2 additions & 0 deletions src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import '@/assets/css/globals.css';
import Html from './Html';
import Providers from './Providers';

export const dynamic = 'force-static';

export async function generateMetadata(): Promise<Metadata> {
const allFeedOptions = await Promise.all(locales.map(createFeedOptions));

Expand Down
18 changes: 16 additions & 2 deletions src/components/NotFound/NotFound.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,25 @@
'use client';

import { Dictionary, getDictionary } from '~/i18n';
import useI18n from '@/hooks/useI18n';
import { H1 } from '../Heading';
import { useEffect, useState } from 'react';

function NotFound() {
const { dict } = useI18n();
const { lang } = useI18n();
const [dictionary, setDictionary] = useState<Dictionary | null>(null);

return <h1>{dict.notFound.message}</h1>;
useEffect(() => {
getDictionary(lang).then(setDictionary);
}, [lang]);

if (!dictionary) return null;

return (
<div className="flex h-screen items-center justify-center">
<H1>{dictionary.notFound.message}</H1>
</div>
);
}

export default NotFound;
16 changes: 12 additions & 4 deletions src/components/NotFound/notFound.test.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,23 @@
import { render, screen } from '@testing-library/react';

import zhTW from '~/i18n/locales/zh-TW';
import en from '~/i18n/locales/en';
import NotFound from '.';

const mockPathname = jest.fn();

jest.mock('next/navigation', () => ({
usePathname: () => mockPathname(),
}));

describe('NotFound component', () => {
it('should render correct element', () => {
it('should render correct element', async () => {
mockPathname.mockReturnValueOnce('/en/test/path');

render(<NotFound />);

const heading = screen.getByRole('heading');
const heading = await screen.findByRole('heading');

expect(heading).toBeInTheDocument();
expect(heading).toHaveTextContent(zhTW.notFound.message)
expect(heading).toHaveTextContent(en.notFound.message)
});
});
15 changes: 6 additions & 9 deletions src/hooks/__tests__/useI18n.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import { renderHook } from '@testing-library/react';
import zhTW from '~/i18n/locales/zh-TW';
import en from '~/i18n/locales/en';
import useI18n from '../useI18n';

const mockPathname = jest.fn();
Expand All @@ -15,19 +13,18 @@ describe('useI18n hook', () => {
});

it.each([
['/test/path', 'zh-TW', zhTW],
['/en/test/path', 'en', en],
['/zh-TW/test/path', 'zh-TW', zhTW],
['/fr-CH/test/path', 'zh-TW', zhTW],
['/test/path', 'zh-TW'],
['/en/test/path', 'en'],
['/zh-TW/test/path', 'zh-TW'],
['/fr-CH/test/path', 'zh-TW'],
])(
'should return the correct language code and dictionary',
(pathname, expectedLang, expectedDictionary) => {
(pathname, expected) => {
mockPathname.mockReturnValueOnce(pathname);

const { result } = renderHook(() => useI18n());

expect(result.current.lang).toBe(expectedLang);
expect(result.current.dict).toStrictEqual(expectedDictionary);
expect(result.current.lang).toBe(expected);
}
);
});
4 changes: 2 additions & 2 deletions src/hooks/__tests__/useIntersectionObserver.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,14 +53,14 @@ describe('useIntersectionObserver hook', () => {
expect(mockIntersectionObserver).toBeCalledTimes(1);
expect(mockObserve).toBeCalledTimes(0);

act(() => result.current.setElementRef(elementRef.current));
act(() => result.current[1](elementRef.current));

expect(mockDisconnect).toBeCalledTimes(0);
expect(mockObserve).toBeCalledTimes(2);
expect(mockObserve).toBeCalledWith(elementRef.current[0]);
expect(mockObserve).toHaveBeenLastCalledWith(elementRef.current[1]);

act(() => result.current.setElementRef(elementRef.current[0]));
act(() => result.current[1](elementRef.current[0]));

expect(mockDisconnect).toBeCalledTimes(1);
expect(mockObserve).toBeCalledTimes(3);
Expand Down
6 changes: 1 addition & 5 deletions src/hooks/useAutoReset.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
import { useEffect, useRef, useState } from 'react';

/**
* Custom hook that resets a value to its initial state after a specified delay.
*
* @param initialValue - The initial value.
* @param resetDelayMs - The reset delay in milliseconds. Default is 1000.
* @returns A tuple containing the current value, and a function to set a new value.
* This hook resets a value to its initial state after a specified delay.
*/
function useAutoReset<T>(initialValue: T, resetDelayMs = 1000) {
const [internalValue, setInternalValue] = useState(initialValue);
Expand Down
13 changes: 4 additions & 9 deletions src/hooks/useI18n.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,15 @@
import { usePathname } from 'next/navigation';
import { defaultLocale } from '~/i18n';
import zhTW from '~/i18n/locales/zh-TW';
import en from '~/i18n/locales/en';
import getLocale from '@/utils/getLocale';

const dictionaries = {
'zh-TW': zhTW,
en,
};

/**
* This hook retrieves the language setting based on the current pathname.
*/
function useI18n() {
const pathname = usePathname();
const lang = getLocale(pathname, defaultLocale);
const dict = dictionaries[lang];

return { lang, dict };
return { lang };
}

export default useI18n;
23 changes: 5 additions & 18 deletions src/hooks/useIntersectionObserver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,15 @@ interface UseIntersectionObserverProps extends IntersectionObserverInit {
elementRef?: RefObject<ElementNode>;
}

type UseIntersectionObserverResponse = [
IntersectionObserverEntry[],
(node: ElementNode) => void
] & {
entry: IntersectionObserverEntry[];
setElementRef: (node: ElementNode) => void;
};

/**
* This Hook provides a way to observe the intersection of a DOM element with its nearest scrollable ancestor or the viewport.
*/
function useIntersectionObserver({
root = null,
threshold = 0,
rootMargin,
elementRef,
}: UseIntersectionObserverProps = {}): UseIntersectionObserverResponse {
}: UseIntersectionObserverProps = {}) {
const [entry, setEntry] = useState<IntersectionObserverEntry[]>([]);
const observerRef = useRef<IntersectionObserver>();
const internalElementRef = useRef<ElementNode>();
Expand Down Expand Up @@ -49,15 +44,7 @@ function useIntersectionObserver({
return () => observerRef.current?.disconnect();
}, [elementRef, setInternalElementRef]);

const result = [
entry,
setInternalElementRef,
] as UseIntersectionObserverResponse;

result.entry = result[0];
result.setElementRef = result[1];

return result;
return [entry, setInternalElementRef] as const;
}

export default useIntersectionObserver;

0 comments on commit 5dc724d

Please sign in to comment.