Search code examples
reactjsnext.jsreact-hooksreact-props

How to pass props to child component in Next.JS 13


I'm having some trouble getting NextJS to work with props and passing them through components. This may be a bit more of a React approach so maybe it doesn't work here but I've tried googling and not many results come back with how to do it. Basically what I'm trying to do is simply pass a useState and setUseState value from my parent to child in order to update inside the child onClick.

However there is an error that seems to be occurring on the Prop type when I pass props through. The only way to avoid it seems to be by using (props: any) however I don't want to do this as it isn't Type safe.

Is what I'm trying to do possible in NextJS or is there a different way to do it? I had a look at getInitial/Static/ServerProps but couldn't get those to work either.

Admittedly I haven't got much experience with Next so perhaps it's a skill issue.

Hopefully that was clear enough, happy to add more detail I probably missed something crucial it's just been plaguing me all day!

Thanks for any help.

Parent component

'use client';
import { useState } from 'react';
import ChildComponent from './childComponent/page';

export default function ParentComponent() {
  const [value, setValue] = useState('hello world')
  return <ChildComponent value={value} setValue={setValue}/>
}

Child component

import React, { Dispatch, SetStateAction } from 'react'

interface Props {
  value: string
  setValue: Dispatch<SetStateAction<string>>
}

const ChildComponent = (props: Props) => {
  return (
    <div>
      <span onClick={() => props.setValue('hello moon')}>{props.value}</span>
    </div>
  )
}

export default ChildComponent

Error

.next/types/app/childComponent/page.ts:26:13
Type error: Type 'OmitWithTag<Props, keyof PageProps, "default">' does not satisfy the constraint '{ [x: string]: never; }'.
  Property 'value' is incompatible with index signature.
    Type 'string' is not assignable to type 'never'.

  24 | 
  25 | // Check the prop type of the entry function
> 26 | checkFields<Diff<PageProps, FirstArg<TEntry['default']>, 'default'>>()
     |             ^

layout.tsx

import ...

const inter = Inter({ subsets: ['latin'] })

export const metadata: Metadata = {
  title: 'Title',
  description: 'Generated by create next app',
}

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en" className=' bg-pink-300 h-screen w-screen content-center justify-center items-center flex'>
      <body className={`${inter.className} h-screen w-screen justify-center items-center flex`}>
      
        {children}
      
      </body>
    </html>
  )
}

next.config.js is default

I have tried with const Component = (props: Props), const Component: React.FC<Props> = (props), Interface/Type { value: any, setValue: any } and all permutations of these to no avail.

The app also builds perfectly fine when running npm run dev and the usestate can be updated but just when running npm run build does it fail.

enter image description here

To the best of my knowledge it looks like the only types that can be passed are those that can be made are those that are valid in an index interface An index signature parameter type must be 'string', 'number', 'symbol', or a template literal type.

interface Props {
  [key: string | number ]: string
}

Because of this I am going to leave it as (props: any) for now...

Edit 2:

Attempted with the components folder mentioned by Sachin and it's working perfectly. Perhaps you can do it the other way too but my brain is too small or it's just not possible.

Here is the classes and dir layout for anyone's future reference...

Parent

'use client';
import { useState } from 'react'
import { ChildComponent } from '@/components/';
 
export default function Page() {
  const [value, setValue] = useState('hello world')
  return <ChildComponent setValue={setValue} value={value} />
}
import React, { Dispatch, SetStateAction } from 'react'

interface Props {
  value: string
  setValue: Dispatch<SetStateAction<string>>
}

const ChildComponent = (props: Props) => {
  return (
    <div>
      <span onClick={() => props.setValue('hello moon')}>{props.value}</span>
    </div>
  )
}

export default ChildComponent

Thanks to all!

enter image description here


Solution

  • Solution:

    Use the components folder, the directory layout should be like

    root
    |
    ├- app
    |  ├ page.tsx
    |  ├ layout.tsx
    |  └ ...
    |
    ├- components
    |  ├ childComponent
    |  | └ ChildComponent.tsx
    |  └ index.ts (optional)
    └ ...
    

    Page.tsx it should simply be:

    'use client';
    import { useState } from 'react'
    import { ChildComponent } from '@/components/';
     
    export default function Page() {
      const [value, setValue] = useState('hello world')
      return <ChildComponent setValue={setValue} value={value} />
    }
    

    ChildComponent.tsx (with props):

    import React, { Dispatch, SetStateAction } from 'react'
    
    interface Props {
      value: string
      setValue: Dispatch<SetStateAction<string>>
    }
    
    const ChildComponent = (props: Props) => {
      return (
        <div>
          <span onClick={() => props.setValue('hello moon')}>{props.value}</span>
        </div>
      )
    }
    
    export default ChildComponent
    

    See my original question for more detail if needed!