Next.js 14: TypeError: s is not a function, while using formData()
Asked Answered
F

2

8

Summary of the program: I am trying to upload an image to Next.js server-less backend. I use formData() to append/set the data and POST it using axios to the backend. In the backend, I am requesting the formData() from NextRequest, changing it into buffer and saving it in the server file system.

Problem: Whenever I fire-up the publish on the front-end I can see error on my Next.js console saying TypeError: s is not a function.

Full error:

TypeError: s is not a function
at PATH\node_modules\next\dist\compiled\next-server\app-page.runtime.dev.js:37:5830
at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
at async tX (PATH\node_modules\next\dist\compiled\next-server\app-page.runtime.dev.js:37:5207)
at async rl (PATH\node_modules\next\dist\compiled\next-server\app-page.runtime.dev.js:38:22994)
at async doRender (PATH\node_modules\next\dist\server\base-server.js:1407:30)
at async cacheEntry.responseCache.get.routeKind (PATH\node_modules\next\dist\server\base-server.js:1571:28)
at async DevServer.renderToResponseWithComponentsImpl (PATH\node_modules\next\dist\server\base-server.js:1479:28)
at async DevServer.renderErrorToResponseImpl (PATH\node_modules\next\dist\server\base-server.js:2104:24)
at async pipe.req.req (PATH\node_modules\next\dist\server\base-server.js:1982:30)
at async DevServer.pipeImpl (PATH\node_modules\next\dist\server\base-server.js:902:25)

Client (page.tsx):

"use client";
import React from "react";
import dynamic from "next/dynamic";
import "react-quill/dist/quill.snow.css";
import { useRouter } from "next/navigation";
import axios from "axios";

// Dynamically import ReactQuill to ensure it's only loaded on the client side
const ReactQuill = dynamic(() => import("react-quill"), { ssr: false });

var toolbarOptions = [
    // toolbar options
];

const BLOG_API = "/api/blogs";

export default function WriteBlogPage() {
    const router = useRouter();
    const [title, setTitle] = React.useState("");
    const [image, setImage] = React.useState<File>();
    const [content, setContent] = React.useState("");
    const [slug, setSlug] = React.useState("");

    const handleContentChange = (value: any) => {
        setContent(value);
    };

    const handleFormSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
        e.preventDefault();

        setSlug(title.replaceAll(" ", "-").toLowerCase());
        
        if (!image || !title || !content || !slug) return;


        try {
            const data = new FormData();
            data.append("image", image, image.name);
            data.append("title", title);
            data.append("content", content);
            data.append("slug", slug);
            const response = await axios.post(BLOG_API, data);
            if (response.status === 200) {
                router.push("/");
            } else {
                console.error("Error posting the blog");
            }
        } catch (error) {
            console.error("Error posting the blog:", error);
        }
    };

    return (
        <form onSubmit={handleFormSubmit}>
            <div className="w-full">
                {
                    <ReactQuill
                        value={content}
                        onChange={handleContentChange}
                        modules={{
                            toolbar: toolbarOptions,
                        }}
                        className="text-center"
                        placeholder="Write an epic"
                    />
                }
            </div>
            <input
                type="file"
                accept="image/*"
                onChange={(e) => setImage(e.target.files?.[0])}
            />
        
            <input
                type="text"
                value={title}
                onChange={(e) => setTitle(e.target.value)}
            />
            <button
                type="submit">
                Publish
            </button>
        </form>
    );
}

Server (route.ts):

// Import necessary modules and models
import { NextRequest, NextResponse } from "next/server";
import { dbConnect as connect } from "@/dbConfig/dbConfig";
import Blog from "@/models/blogModel";
import NodeCache from "node-cache";
import path, { join } from "path";
import { writeFile } from "fs";

// Connect to the database
connect();

// NodeCache configuration for caching
const cache = new NodeCache({
    stdTTL: 1800,
    checkperiod: 300,
});

export const config = {
    api: {
        bodyParser: false,
    },
};

// POST route for creating a new blog
export async function POST(req: NextRequest, res: NextResponse) {
    try {
        const data = await req.formData();
        const image: File | null = data.get("image") as unknown as File;
        const title: String | null = data.get("title") as unknown as String;
        const content: String | null = data.get("content") as unknown as String;
        const slug: String | null = data.get("slug") as unknown as String;

        if (!image || !title || !content || !slug) {
            return NextResponse.json({
                success: false,
                message: "Please pass all necessary data",
            });
        }

        const blogTitle = await Blog.findOne({ title });

        if (blogTitle) {
            return NextResponse.json({
                success: false,
                message:
                    "Already posted. More than one blog of the same title is not allowed",
            });
        }

        const bytes = await image.arrayBuffer();
        const buffer = Buffer.from(bytes);

        const path = join("/","public/uploads/blogs/cover", image.name);

        await writeFile(path, buffer, (error) => {
            return NextResponse.json({
                success: false,
                message: "File save unsuccessful. Please try again.",
            });
        });

        const coverImagePath = join("/uploads/blogs/cover", image.name)

        const blog = new Blog({
            coverImagePath,
            title,
            content,
            slug,
        });

        const savedBlog = await blog.save();

        cache.del("blogs");

        return NextResponse.json({
            message: "Blog posted successfully",
            success: true,
            savedBlog,
        });
    } catch (error: any) {
        return NextResponse.json({ error: error.message }, { status: 500 });
    }
}

What I tried:

  • Using NextApiRequest/NextApiResponse: Says I cannot access .formData() as Property 'formData' does not exist on type 'NextApiRequest'.

  • Using multer & store the image, but still I get the same error on console.

Freudberg answered 11/11, 2023 at 10:34 Comment(2)
Did you find a solution to this issue by any chance?Gothicize
@Gothicize try updating the API endpoint on the client-side. The issue I had was a misconfigured API endpoint.Fang
K
1

i just got the same problem few hours ago.

It turns out that i didn't put the api folder inside the app folder which is the correct place to hold it, and the error i think it was because the config or dependency issue cause the problem (due to i didn't have it in the right place...)

You can check on the official document of Next.js, i figured it out when i read this page, just checkout the Convention part, it said: "Route Handlers are defined in a route.js|ts file inside the app directory: Route Handlers can be nested inside the app directory, similar to page.js and layout.js. But there cannot be a route.js file at the same route segment level as page.js."

[https://nextjs.org/docs/app/building-your-application/routing/route-handlers][https://nextjs.org/docs/app/building-your-application/routing/route-handlers]

Hope this would help you ....

Kulseth answered 30/11, 2023 at 5:54 Comment(0)
L
1

The error is not obvious, but it points to an unresolved route, so basically, your API handler doesn't exist on path /api/blogs.

Leanneleanor answered 7/1 at 23:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.