Home > Software engineering >  Uncaught (in promise) TypeError: hobbies is not iterable at createHobby when using Prisma and Postgr
Uncaught (in promise) TypeError: hobbies is not iterable at createHobby when using Prisma and Postgr

Time:12-29

So I'm very new to Prisma, and actually also to React. My Postgresql database works, but I'm trying to show the stored data in my application. My very simple table in the schema file looks like this:

model Hobby {
  id        Int  @id @default(autoincrement())
  title     String
}

I'm using useContext to distribute my createHobby functionality, this is what the context file looks like.

export async function getServerSideProps() {
    const hobbies: Prisma.HobbyUncheckedCreateInput[] = await prisma.hobby.findMany();
    return {
        props: {initialHobbies: hobbies},
    };
}

export const HobbyContext = createContext({})
function Provider({ children, initialHobbies }){

    const [hobbies, setHobbies] = useState<Prisma.HobbyUncheckedCreateInput[]>(initialHobbies);

    const createHobby = async (title) => {

        const body: Prisma.HobbyCreateInput = {
            title,

        };
        await fetcher("/api/create-hobby", {hobby : body});
        console.log(hobbies);

        const updatedHobbies = [
            ...hobbies,
            body
        ];
        setHobbies(updatedHobbies);

    const contextData = {
        hobbies,
        createHobby,

    }
    return (
        <HobbyContext.Provider value={contextData}>
            {children}
        </HobbyContext.Provider>
    );
};

export default HobbyContext;
export {Provider};

Here I get the following error Uncaught (in promise) TypeError: hobbies is not iterable at createHobby. Which refers to the const updatedHobbies = [...hobbies, body];

For more context, I have a HobbyCreate.tsx which creates a little hobby card that renders the title of the hobby, which is submitted with a form.

function HobbyCreate({updateModalState}) {
    const [title, setTitle] = useState('');
    const {createHobby} = useHobbiesContext();
    
    const handleChange = (event) => {
        setTitle(event.target.value)
    };

    const handleSubmit = (event) => {
        event.preventDefault();
        createHobby(title);
    };
    return (
        ...
        <form onSubmit={handleSubmit}></form>
        ...


    )

I can't really figure out what is going wrong, I assume somewhere when creating the const [hobbies, setHobbies] and using the initialHobbies.

CodePudding user response:

I don't think you're using the Context API correctly. I've written working code to try and show you how to use it.

Fully typed hobby provider implementation

This is a fully typed implementation of your Provider:

import { createContext, useState } from 'react';
import type { Prisma } from '@prisma/client';
import fetcher from 'path/to/fetcher';

export type HobbyContextData = {
  hobbies: Prisma.HobbyCreateInput[]
  createHobby: (title: string) => void
};

// you could provide a meaningful default value here (instead of {})
const HobbyContext = createContext<HobbyContextData>({} as any);

export type HobbyProviderProps = React.PropsWithChildren<{
  initialHobbies: Prisma.HobbyCreateInput[]
}>;

function HobbyProvider({ initialHobbies, children }: HobbyProviderProps) {
  const [hobbies, setHobbies] = useState<Prisma.HobbyCreateInput[]>(initialHobbies);

  const createHobby = async (title: string) => {
    const newHobby: Prisma.HobbyCreateInput = {
      title,
    };

    await fetcher("/api/create-hobby", { hobby: newHobby });

    console.log(hobbies);

    setHobbies((hobbies) => ([
      ...hobbies,
      newHobby,
    ]));
  };

  const contextData: HobbyContextData = {
    hobbies,
    createHobby,
  };

  return (
    <HobbyContext.Provider value={contextData}>
      {children}
    </HobbyContext.Provider>
  );
}

export default HobbyContext;
export { HobbyProvider };

Using HobbyProvider

You can use HobbyProvider to provide access to HobbyContext for every component wrapped inside it. For example, to use it in every component on /pages/hobbies your implementation would look like:

// /pages/hobbies.tsx
import { useContext, useState } from 'react';
import HobbyContext, { HobbyProvider } from 'path/to/hobbycontext';

export default function HobbiesPage() {
  // wrapping the entire page in the `HobbyProvider`
  return (
    <HobbyProvider initialHobbies={[{ title: 'example hobby' }]}>
      <ExampleComponent />
      {/* page content */}
    </HobbyProvider>
  );
}

function ExampleComponent() {
  const { hobbies, createHobby } = useContext(HobbyContext);

  const [title, setTitle] = useState('');

  return (
    <div>
      hobbies: {JSON.stringify(hobbies)}
      <div>
        <input
          value={title}
          onChange={(e) => setTitle(e.target.value)}
        />
        <button onClick={() => createHobby(title)}>Create hobby</button>
      </div>
    </div>
  );
}

Similarly, to make the context available throughout your entire website, you can use HobbyProvider in /pages/_app.tsx.

Using getServerSideProps

To retrieve the initialHobbies from the database, your getServerSideProps would look something like this:

// /pages/hobbies.tsx
import type { Hobby } from '@prisma/client';

export async function getServerSideProps() {
  // note: there is no need to use `Hobby[]` as prisma will automatically give you the correct return
  // type depending on your query
  const initialHobbies: Hobby[] = await prisma.hobby.findMany();

  return {
    props: {
      initialHobbies,
    },
  };
}

You would have to update your page component to receive the props from getServerSideProps and set initialHobbies on HobbyProvider:

// /pages/hobbies.tsx
import type { InferGetServerSidePropsType } from 'next';

export default function HobbiesPage({ initialHobbies }: InferGetServerSidePropsType<typeof getServerSideProps>) {
  return (
    <HobbyProvider initialHobbies={initialHobbies}>
      <ExampleComponent />
    </HobbyProvider>
  );
}

Note your page component and getServerSideProps function have to be exported from the same file

  • Related