skip to Main Content

I am writing a simple React CRUD app, using NextJS 13 and its app router.

I have a route, in which page.tsx is a client component.

page.tsx

'use client'

import DataGrid from './DataGrid'

const List = (props) => (
   <DataGrid>...</DataGrid>
)

export default List

DataGrid.tsx

'use client'
const DataGrid = ({entity, identifier}) => {
   const gridIdentifier = `${window.location.pathname}-${entity}`;
   const gridSettings = JSON.parse(localStorage.getItem(gridIdentifier) || '{}');
   
   return ...
}

export default DataGrid

And when I try to hit my route, the console shows

 ✓ Compiled in 1638ms (12671 modules)
 X src/common/components/DataGrid/DataGrid.tsx (55:26) @ DataGrid
 X ReferenceError: window is not defined
    at DataGrid (./src/common/components/DataGrid/DataGrid.tsx:40:28)
  53 |   const router = useRouter();
  54 |   const ModalFormComponent = modalFormComponent as React.ElementType;
> 55 |   const gridIdentifier = `${window.location.pathname}-${entity}`;
     |                          ^
  56 |   const gridSettings = JSON.parse(localStorage.getItem(gridIdentifier) || '{}');
  57 |   
 ✓ Compiled /not-found in 758ms (12676 modules)

Isn’t the use client directive supposed to allow the usage of browser APIs ?
I shouldn’t have to use useEffect like it was the case before NextJS 13.

I tried to add the conventional check if (typeof window === 'undefined'), but ended up with localStorage being undefined as well.

I also tried to transform the List parent into a server component, and having all its dependent components being client components whenever necessary, but it ended up being cubersome and useless.

2

Answers


  1. I believe "use client" still renders your component once on the server before rendering again on the client. Using the client-only npm package, dynamically importing your client component, or wrapping in a useEffect should do the trick. Here’s the next docs where they talk about client component rendering: https://nextjs.org/docs/app/building-your-application/rendering/client-components and here’s where they talk about the client-only package https://nextjs.org/docs/app/building-your-application/rendering/composition-patterns#keeping-server-only-code-out-of-the-client-environment

    'use client'
    import 'client-only'
    
    const DataGrid = ({entity, identifier}) => {
        ...
    }
    
    

    or

    const DataGrid = dynamic(() => import("./DataGrid"), { ssr: false });
    
    const List = (props) => (
       <DataGrid>...</DataGrid>
    )
    

    or

    useEffect(() => {
        const gridIdentifier = `${window.location.pathname}-${entity}`;
        const gridSettings = JSON.parse(localStorage.getItem(gridIdentifier) || '{}');
    }, []}
    
    Login or Signup to reply.
  2. yes,same problem found a very simple and useful solution is wrap your code of window by useEffect hook,such as:

    useEffect(()=>{
    const gridIdentifier=`${window.location.pathname}-${entity}`;
    const gridSettings = JSON.parse(localStorage.getItem(gridIdentifier) || '{}');
    },[])
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search