skip to Main Content

When working on a Next.JS project (hosted on Heroku) I have the following isssue.

This is the content of the main file (for my problem) DB_ChanList.tsx:

'use client';

import Link from 'next/link';
import channelsPool from "@/lib/channels";
import React from 'react';
import {useState,useEffect} from 'react';


export default function DB_ChanList() {
  const [listDisplay,setListDisplay] = useState(true);
  const theChannels = channelsPool()


  const swapDisplay = (e: React.MouseEvent<HTMLButtonElement>) => {
    e.preventDefault()
    setListDisplay(!listDisplay)
  } /* End of swapDisplay */

  return <div>
        {listDisplay &&
      Object.values(theChannels).map(item => (
                <div key={item as string}>
          <button onClick={(e)=>swapDisplay(e)} value={item}>
            <b>{item}</b>
          </button>
                </div>
            ))
        }
        {!listDisplay &&
      <div>THE:unitID::</div>
        }
    </div>
} /* End of DB_ChanList */

This is the content of the file channels.tsx:

import mongoose, {Schema,Model,models} from "mongoose"
import connectMDB from "./mongoDB"


export default async function channelsPool() {
    const speakSchema = new Schema({
        channelID: {
            type: String,
            required: true
        },
        voiceRecordID: {
            type: String,
            required: true
        },
        timeStamp: {
            type: Number,
            required: true
        }
    })

    interface SpeakDoc extends Document {
        channelID: string
        voiceRecordID: string
        timeStamp: number
    }

    interface SpeakModel extends Model<SpeakDoc> {}

    const theModel = (models && models.Voice) ? (models.Voice as SpeakModel) :
                                            mongoose.model<SpeakDoc, SpeakModel>
                                                    ('Voice',speakSchema,'vxads')

    await connectMDB()

    try {
        console.log('MDB_URI_AUDIO(T)=',process.env.MDB_URI_AUDIO)
        const theResult = await theModel
        .find({},'channelID').distinct('channelID')
  
        return theResult
    } catch (error) {
        console.log('MDB_URI_AUDIO(E)=',process.env.MDB_URI_AUDIO)
        console.error("Error in channelsPool:", error);
        throw error; // Rethrow the error to propagate it.
    }
} /* End of channelsPool */

And finally this is the content of the file mongoDB.tsx:

import mongoose from "mongoose"

const uri = process.env.MDB_URI_AUDIO!,
            connectMDB = async () => mongoose.connect(uri)

export default connectMDB

With all the sources above I get this error message, relating to the last file :

TypeError: mongoose__WEBPACK_IMPORTED_MODULE_0___default().connect is not a function

After some investigations searching the net, it seems like the code in mongoDB.tsx, though working fine when used in a server component is not appropriate for use in a client component. What is the way for me to solve this situation and access my database here ?

2

Answers


  1. Why using it outside server component??? Webpack seems to find it hard to bundle it correctly because client side code is bundled. Mongoose uses native nodejs modules and these modules can not be packed by webpack for client side serving.

    Edit:
    Always remember that nodejs native modules can not be packed by webpack for client side serving. There are workarounds but it doesnt worth it trust me…. Unless u want 100+ MB bundles

    Login or Signup to reply.
  2. You are attempting to connect to mongoose in a client component.

    There are two methods I could think of to solve this.

    Method 1

    You may wrap the wrap the client components that consumes channelPools with a server component. In effect running the function on a server component before passing it down to a client component as props

    New component <DB_ChanList_SC />

    // DB_ChanList_SC.tsx
    import React from "react";
    
    import channelsPool from "@/lib/channels";
    import DB_ChanList from "./DB_ChanList";
    
    const DB_ChanList_SC = async () => {
      const theChannels = await channelsPool();
      return <DB_ChanList theChannels={theChannels} />;
    };
    
    export default DB_ChanList_SC;
    

    Editied <DB_ChanList /> takes theChannels as props

    // DB_ChanList.tsx
    "use client";
    
    import Link from "next/link";
    import React from "react";
    import { useState, useEffect } from "react";
    
    export default function DB_ChanList({
      theChannels,
    }: {
      theChannels: string[];
    }) {
      const [listDisplay, setListDisplay] = useState(true);
    
      const swapDisplay = (e: React.MouseEvent<HTMLButtonElement>) => {
        e.preventDefault();
        setListDisplay(!listDisplay);
      }; /* End of swapDisplay */
    
      return (
        <div>
          {listDisplay &&
            Object.values(theChannels).map((item) => (
              <div key={item as string}>
                <button onClick={(e) => swapDisplay(e)} value={item}>
                  <b>{item}</b>
                </button>
              </div>
            ))}
          {!listDisplay && <div>THE:unitID::</div>}
        </div>
      );
    }  /* End of DB_ChanList */
    

    And where you used <DB_ChanList />, you would replace it with the server component

        <>
          {/* <DB_ChanList /> */}
          <DB_ChanList_SC />
        </>
    

    Note that this method only works if <DB_ChanList_SC /> is used inside other server components and will not work if used inside a client component.

    Method 2

    Make an API route that responds with the results of channelPool

    Create a new API endpoint at /api/channels-pool, this endpoint will run the function and return the results

    // src/app/api/channels-pool/route.ts
    
    import channelsPool from "@/lib/channels";
    import { NextResponse } from "next/server";
    
    export type ChannelsPoolResponseType = Awaited<ReturnType<typeof channelsPool>>;
    
    export const GET = async (req: Request) => {
      const theChannels = await channelsPool();
      return NextResponse.json(theChannels);
    };
    

    Modify <DB_ChanList /> to use fetch to get data

    "use client";
    
    import type { ChannelsPoolResponseType } from "@/app/api/channels-pool/route";
    import Link from "next/link";
    import React from "react";
    import { useState, useEffect } from "react";
    
    export default function DB_ChanList() {
      const [theChannels, setTheChannels] = useState<ChannelsPoolResponseType>([]);
      const [listDisplay, setListDisplay] = useState(true);
    
      const swapDisplay = (e: React.MouseEvent<HTMLButtonElement>) => {
        e.preventDefault();
        setListDisplay(!listDisplay);
      }; /* End of swapDisplay */
    
      const fetchChannels = async () => {
        const res = await fetch("/api/channels-pool");
        const json = await res.json();
        setTheChannels(json);
      };
    
      useEffect(() => {
        fetchChannels();
      }, []);
    
      return (
        <div>
          {listDisplay &&
            Object.values(theChannels).map((item) => (
              <div key={item as string}>
                <button onClick={(e) => swapDisplay(e)} value={item}>
                  <b>{item}</b>
                </button>
              </div>
            ))}
          {!listDisplay && <div>THE:unitID::</div>}
        </div>
      );
    } /* End of DB_ChanList */
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search