I'm building mini forum feature in my app using Next.js. The forum has a feature where you can delete your forum post if you are the author. I try to make a delete request using the HTML form and axios.delete. It hits the API endpoint properly and surprisingly executes the delete (from a database) yet gives out an error that says 405 Method Not Allowed. I have already read and applied what is written in the Vercel Docs regarding this issue but to no avail. Code below
next.config.js
/** #type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
// typescript: true,
headers: [
{ key: "Access-Control-Allow-Credentials", value: "true" },
{ key: "Access-Control-Allow-Origin", value: "http://localhost:3000" },
{
key: "Access-Control-Allow-Methods",
value: "GET,OPTIONS,PATCH,DELETE,POST,PUT",
},
{
key: "Access-Control-Allow-Headers",
value: "X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version",
},
],
};
module.exports = nextConfig;
school-forum/[id].tsx
import React, { useState, useEffect } from "react";
import axios from "axios";
import styles from "./index.module.css";
interface Props {
currentId: number;
}
const ButtonOptions: React.FC<Props> = ({currentId}) => {
const thisAPI: string = `/school-forum/${currentId}`;
async function handleDelete(): Promise<void> {
await axios.delete(thisAPI, {
data: {
currentId,
},
});
return void 0;
}
return (
<>
...
<div>
<form
className={styles.optionForms}
onSubmit={handleDelete}
>
<h2 className={styles.optionH2Header}>Option Delete</h2>
<hr className="horizontalRuleYellow" />
<b className={styles.deleteMessage}>
Notice: This action cannot be reversed
</b>
<p className={styles.deleteMessage}>
Are you sure you want to proceed to deletion?
</p>
<hr className="horizontalRuleYellow" />
<div className={styles.optionButtons}>
<button type="submit">Execute Option</button>
</div>
</form>
</div>
</>
);
};
export default ButtonOptions;
api/school-forum/[id].tsx
import { NextApiRequest, NextApiResponse } from "next";
import dbExecute from "../../../_operations/db/db";
export default async function (
req: NextApiRequest,
res: NextApiResponse
): Promise<void> {
if (req.method === "DELETE") {
try {
const { currentId } = req.body;
res.setHeader('Access-Control-Allow-Methods', 'GET,OPTIONS,PATCH,DELETE,POST,PUT')
res.setHeader(
'Access-Control-Allow-Headers',
'X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version'
)
const sql: string = `
DELETE FROM forum_question_table
WHERE question_id = ?;`;
await dbExecute(sql, [currentId]);
// this executes but errors
res.redirect("http://localhost:3000/school-forum");
return void 0;
} catch (error: unknown) {
console.log(error);
console.log("error");
res.status(500).redirect("http://localhost:3000/500");
return void 0;
}
}
}
Figured it out. Turns out I can't redirect in the API folder with a delete method since that results in a weird 405 error. So what I did is just res.status(200).send("") in the hitted API endpoint and router.push("/school-forum") in the front end component for the redirect.
...
const ButtonOptions: React.FC<Props> = ({currentId}) => {
const thisAPI: string = `/school-forum/${currentId}`;
async function handleDelete(): Promise<void> {
await axios.delete(thisAPI, {
data: {
currentId,
},
});
router.push("/school-forum")
return void 0;
}
...
then remove res.redirect("http://localhost:3000/school-forum");
Related
Just to mention that the answers in this Question did not work for me.
So I have a page which used Static Site Generation(SSG) and no matter how i export it, while deploying in vercel the error always shows "found page without a React Component as default export".
This is my whole file:
File name : [productId].js
import Head from "next/head";
import React from "react";
import ProductDetails from "../../components/Product/ProductDetails/ProductDetails";
export default function index({ product }) {
return (
<>
{/* <Head>
</Head> */}
<ProductDetails product={product} />
</>
);
}
export async function getStaticPaths() {
const res = await fetch(
`${process.env.NEXT_PUBLIC_API}/api/v1/user/products?page=${"all"}`,
{
method: "GET",
headers: {
"Content-Type": "application/json",
},
}
);
const data = await res.json();
const paths = data.data.map((product) => {
return {
params: { productId: product._id.toString() },
};
});
console.log("paths", paths);
return {
paths: paths,
fallback: false,
};
}
export const getStaticProps = async (context) => {
const { params } = context;
console.log("params", params);
const { productId } = params;
const res = await fetch(
`${process.env.NEXT_PUBLIC_API}/api/v1/user/products/${productId}`,
{
method: "GET",
headers: {
"Content-Type": "application/json",
},
}
);
const data = await res.json();
if (data.status) {
return {
props: {
product: data.data,
},
};
}
};
Can anyone tell me what I am doing wrong here. Becuase I have used this same way of exporting which is "export default function index" in other files and they don't seem to have a problem but only in this file I am having this issue.
I am using NextJS with NextAuth with google and email providers. Unfortunately, the session returns only few fields that does not include userId of the user from the database.
I created however a function that I intend to use with every getServerSideProps request. The function returns the following:
{
user: {
name: 'daniel sas',
email: 'emailofuser#gmail.com',
image: 'https://lh3.gooleusercontent.com/a/AEdFTp6r44ZwqcfJORNnuYtbVv_LYbab-wv5Uyxk=s96-c',
userId: 'clbcpc0hi0002sb1wsiea3q5d'
},
expires: '2022-12-17T20:18:52.580Z'
}
The problem is I am getting an error that does not allow me to pass the props in the page:
Error: Your `getServerSideProps` function did not return an object. Did you forget to add a `return`?
In the function I get the user by the email, and attach the userId.
import { getSession } from "next-auth/react";
import prisma from './prisma'
// This function get the email and returns a new session object that includes
// the userId
export const requireAuthentication = async context => {
const session = await getSession(context);
const errorOrUserNotFound = () => {
return {
redirect: {
destination: '/signup',
permanent: false
}
}
}
// If there is no user or there is an error ret to signup page
if (!session) {
errorOrUserNotFound();
}
// If the user is not found return same redirect to signup
else {
try {
const user = await prisma.user.findUnique({where: { email: session.user.email }});
if (!user) return errorOrUserNotFound();
// Must return a new session here that contains the userId...
else {
const newSession = {
user: {
...session.user,
userId: user.id
},
expires: session.expires
};
console.log(newSession);
return {
props: {
session: newSession
}
}
}
}
catch (error) {
if (error) {
console.log(error);
}
}
}
}
The react component looks like this. In the getServerSideProps i return the await function. The problem is that when I log the prop in the serverside, I get the following:
{
props: { session: { user: [Object], expires: '2022-12-17T20:18:52.580Z' } }
}
However, if i log the props in the clientside, I get an empty object...
//Clientside compoen
import { getSession } from "next-auth/react"
import { Fragment, useState } from "react";
import { requireAuthentication } from "../../lib/requireAuthentication";
import CreateListModal from "./CreateListModal";
const DashboardPage = props => {
const [loading, setloading] = useState(false);
console.log(props);
return (
<section className="border-4 border-orange-800 max-w-5xl mx-auto">
<CreateListModal userId={props.userId} loading={loading} setloading={setloading} />
</section>
)
}
export const getServerSideProps = async context => {
const session = await getSession(context);
const reqAuth = await requireAuthentication(context);
console.log(reqAuth);
return reqAuth
}
export default DashboardPage;
I redirect users to the login page, when they try to access a page without authentication.
I then wanna show them a Message.
But I am not able to pass parameters in the redirect. What causes the issue and how to do it properly?
// PAGE NEEDS AUTH / REDIRECT TO LOGIN WITH MESSAGE
// application
import { GetServerSideProps } from 'next';
import SitePageProducts from '../../components/site/SitePageProducts';
import axios from 'axios';
import { getSession } from 'next-auth/react';
import url from '../../services/url';
import { ProductFields } from '../../lib/ebTypes';
function Page() {
return <SitePageProducts />;
}
export default Page;
export const getServerSideProps: GetServerSideProps = async (context) => {
const session = await getSession(context)
if (session) {
const products = await axios.get(`${process.env.NEXT_PUBLIC_API_URL}/products`, {
}).then(res => {
console.log('res :>> ', res);
return res.data.products as ProductFields[]
}).catch(err => console.log(err));
console.log('products :>> ', products);
return {
props: {
loading: true,
token: session.user.token,
}
}
} else {
return {
redirect: {
permanent: false,
destination: url.accountSignIn().href,
props: { test: "Message from inside Redirect" }
},
props: {
params: { message: "Message from inside props" },
query: {
message: 'Message from inside props query'
},
message: 'Message from inside props root'
},
};
}
}
// LOGIN PAGE, SHOULD CONSUME AND SHOW MESSAGE WHY LOGIN IS NEEDED
import { GetServerSideProps } from 'next';
import AccountPageLogin from '../../components/account/AccountPageLogin';
import url from '../../services/url';
import { getSession } from "next-auth/react"
function Page(props: any) {
return <AccountPageLogin {...props} />;
}
export default Page;
export const getServerSideProps: GetServerSideProps = async (ctx) => {
// ALL CTX queries / props are empty?????
// CURRENT: query:{} --- EXPECTING: query: {message: "MY MESSAGE"}
console.log('ctx accountpagelogin::: :>> ', ctx);
const session = await getSession(ctx)
if (session) {
return {
redirect: {
destination: url.accountDashboard().href,
permanent: false,
},
};
}
return {
props: {},
};
};
I'm using Next.js with Typescript, and I'm having some troubles to correctly type my props that getServerSideProps would return me. On getServerSideProps, or as I call it, getServerSidePropsImpl, I check user authentication and decide if I give it a redirect or the data for it to initialize. The problem with this is that Typescript doesn't correctly type my props, or give me some errors.
So, i had the idea to :
// /pages/sheet/1.tsx
import React from 'react';
import { GetServerSidePropsContext, InferGetServerSidePropsType } from 'next';
import database from '../../utils/database';
import { sessionSSR } from '../../utils/session';
export default function Sheet1(props: InferGetServerSidePropsType<typeof getServerSidePropsImpl>): JSX.Element {
//...Do some stuff...
return <></>;
}
async function getServerSidePropsImpl(ctx: GetServerSidePropsContext) {
const player = ctx.req.session.player;
if (!player) {
return {
redirect: {
destination: '/',
permanent: false
},
props: {
playerID: 0,
playerInfo: []
}
};
}
const playerID = player.id;
const results = await Promise.all([
database.playerInfo.findMany({
where: {
player_id: playerID
},
select: {
info: true,
value: true
},
})
]);
return {
props: {
playerID,
playerInfo: results[0]
}
};
}
export const getServerSideProps = sessionSSR(getServerSidePropsImpl);
To make sure Typescript would correctly type my props object, I had to put generic values when returning a redirect:
return {
redirect: {
destination: '/',
permanent: false
},
props: {
playerID: 0,
playerInfo: []
}
};
Is there any issues to this approach, giving the amount of props will increase as I make this component?
I'm trying to do a credentials auth with next-auth. I have to use a custom sign-in page and I absolutely can't make it work for approximately one entire week.
I have :
// [...nextauth.js]
import NextAuth from 'next-auth';
import Providers from 'next-auth/providers';
import axios from '#api/axios';
const options = {
providers: [
Providers.Credentials({
async authorize(credentials) {
const { data: user, status } = await axios.post('/users/authentication', credentials);
if (user && status === 200) {
return user;
} else {
throw new Error('error message');
}
}
})
],
pages: {
signIn: '/profil/authentication/login',
error: '/profil/authentication/login'
},
session: {
jwt: true,
maxAge: 30 * 24 * 60 * 60 // 30 days
},
debug: true
};
export default (req, res) => NextAuth(req, res, options);
and :
// profil/authentication/login
import { signOut, useSession } from 'next-auth/client';
import AuthenticationForm from '#components/auth/authenticationForm';
import Layout from '#components/layout';
const Login = () => {
const [session] = useSession();
const intl = useIntl();
return (
<Layout>
{!session && <AuthenticationForm />}
{session && (
<>
Signed in as {session.user.email}
<br />
<button onClick={signOut}>Sign out</button>
</>
)}
</Layout>
);
};
export default Login;
// authenticationForm.js
import { signIn, csrfToken } from 'next-auth/client';
import { useRouter } from 'next/router';
import { useEffect, useState } from 'react';
import PasswordInput from '#components/auth/passwordInput';
import Button from '#components/form/button';
import TextInput from '#components/form/textInput';
const AuthenticationForm = ({ csrf }) => {
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const [error, setError] = useState('');
const router = useRouter();
const handleChangeUsername = ({ target: { value } }) => setUsername(value);
const handleChangePassword = ({ target: { value } }) => setPassword(value);
const handleLogin = () => {
signIn('credentials', {
username,
password,
callbackUrl: `${window.location.origin}/profil`
})
.then((res) => {
console.log('form::res -> ', res);
router.back();
})
.catch((e) => {
console.log('form::e -> ', e);
setError('login error');
});
};
useEffect(() => {
if (router.query.error) {
setError(router.query.error);
setUsername(router.query.username);
}
}, [router]);
return (
<form onSubmit={handleLogin}>
<TextInput
name="username"
value={username}
onChange={handleChangeUsername}
/>
<PasswordInput handleChange={handleChangePassword} />
{error && <div>{error}</div>}
<Button type="submit">
connexion
</Button>
<input name="csrfToken" type="hidden" defaultValue={csrf} />
</form>
);
};
AuthenticationForm.getInitialProps = async (context) => {
return {
csrf: await csrfToken(context)
};
};
export default AuthenticationForm;
And for sure a NEXTAUTH_URL=http://localhost:3000 in .env.local.
If I go on /profil/authentication/login, I see my form and when I click connect, I always some errors like : "Failed to fetch", nothing more, or :
[next-auth][error][client_fetch_error] (2) ["/api/auth/csrf", TypeError: Failed to fetch]
https://next-auth.js.org/errors#client_fetch_error
Even if I try to delete all the csrf handling in my form and let sign-in "do it alone yea".
I'm really stuck with this lib and I most likely will change for another one but I would like to know what am I doing wrong? Is there a FULL example with custom sign-in page and errors handled on the same sign-in page. This is so basic that I can't understand why I don't find one easily.
#Tralgar
I think that problem is related to CSRF policy on your backend, if you are on localhost then localhost:3000 and localhost:2000 is like two different domains. Just make sure if you have your frontend domain in your backend cors policy (if on localhost it must be with a port)
I was able to fix the error by deleting the .next build folder and creating a new build by running npm run build