I'm new to NextJS and I'm trying to create a website for which I've started with a navigation bar component, which I want to make sure it's responsive based on the viewport dimensions. Currently I have this folder structure:
Where I'm attaching a context from src/context/ViewportContext.tsx
into the main layout at src/app/layout.tsx
, as follows:
import type { Metadata } from 'next';
import { Inter } from 'next/font/google';
import './globals.css';
import { ViewportProvider } from '@/context/ViewportContext';
const inter = Inter({ subsets: ["latin"] });
export const metadata: Metadata = {
title: 'Create Next App',
description: 'Generated by create next app',
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang='en'>
<body className={inter.className}>
<ViewportProvider>
{children}
</ViewportProvider>
</body>
</html>
);
}
The ViewportContext.tsx
file looks like this:
'use client';
import React, { createContext, useContext, useState, useEffect, ReactNode } from 'react';
type ViewportContextType = {
width: number;
height: number;
};
const ViewportContext = createContext<ViewportContextType | undefined>(undefined);
export const ViewportProvider = ({ children }: { children: ReactNode }) => {
const [size, setSize] = useState<ViewportContextType>({
width: 0, // Initialize with 0, because the actual value will be set on the client
height: 0,
});
useEffect(() => {
if (typeof window !== 'undefined') {
const handleResize = () => {
setSize({
width: window.innerWidth,
height: window.innerHeight,
});
};
// Sets initial size
handleResize();
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}
}, []);
return (
<ViewportContext.Provider value={size}>
{children}
</ViewportContext.Provider>
);
}
export const useViewport = () => {
const context = useContext(ViewportContext);
if (!context) {
throw new Error('useViewport must be used within a ViewportProvider');
}
return context;
}
so as you see, it also has a useViewport
hook which I'm planning to use to obtain the viewport dimensions and work with them elsewhere. My main page file, at src/app/page.tsx
currently looks like this:
import NavBar from '@/components/NavBar';
export default function Home() {
return (
<>
<NavBar />
</>
);
}
and the NavBar
component I'm specifying which is at src/components/NavBar/index.tsx
looks like this:
import Image from 'next/image';
import styles from './NavBar.module.css';
import useDeviceType from '@/utils/useDeviceType';
const NavBar: React.FC = () => {
const { isTabletOrSmaller, isDesktopOrLarger } = useDeviceType();
console.log(isTabletOrSmaller);
return (
<div className={styles.container}>
<Image
alt='GreenDream logo'
src='/images/logo.svg'
width={162}
height={46}
/>
<Image
alt='Sandwhich'
className={styles.sandwhich}
src='/images/sandwhich.svg'
width={36}
height={32}
/>
</div>
)
}
export default NavBar;
For now, I'm trying to test the functionality of the hook, useDeviceType
which is at src/utils/useDeviceType.tsx
:
import { useViewport } from '@/context/ViewportContext';
const useDeviceType = () => {
const { width } = useViewport();
const isMobile = width <= 768;
const isTablet = width > 768 && width <= 1024;
const isDesktop = width > 1024 && width <= 1440;
const isLargeDesktop = width > 1440;
const isTabletOrSmaller = width <= 1024;
const isDesktopOrLarger = width > 1024;
return {
isMobile,
isTablet,
isDesktop,
isLargeDesktop,
isTabletOrSmaller,
isDesktopOrLarger,
};
}
export default useDeviceType;
If all worked correctly, it should simply print true
if my viewport is smaller than 1024px, but as I run I get this error, useViewport is not a function
:
I'm not really sure why it seems that the import doesn't work for the useViewport
hook, as another function in that file imports correctly in layout.tsx
. Also, my tsconfig.json
currently looks like this:
{
"compilerOptions": {
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
}
As far as I understand, the line "@/*": ["./src/*"]
should allow to import anything from the src
folder so long as I define its import directory as @/
right? So what could be causing this issue? Is there a configuration I forgot to add? If anyone knows or has a insight on this please let me know.
React hooks must be used within client components. Converting the Nav component to a client component using the "use client"
directive at the top of src/components/NavBar/index.tsx
worked for me: