I have a problem with my nextjs pizzashop application. When i run the app on my computer i get this error :
The error goes away after the page refreshes.
This is my index.js
import axios from "axios";
import Head from "next/head";
import { useState } from "react";
import Add from "../components/Add";
import AddButton from "../components/AddButton";
import Featured from "../components/Featured";
import PizzaList from "../components/PizzaList";
import styles from "../styles/Home.module.css";
export default function Home({ pizzaList }) {
const [close, setClose] = useState(true);
return (
<div className={styles.container}>
<Head>
<title>Pizza Restaurant in New york</title>
<meta name="description" content="Best pizza shop in town" />
<link rel="icon" href="/favicon.ico" />
</Head>
<Featured />
{<AddButton setClose={setClose} style={{ display: "none" }} />}
<PizzaList pizzaList={pizzaList} />
{!close && <Add setClose={setClose} />}
</div>
);
}
export const getServerSideProps = async (ctx) => {
const myCookie = ctx.req?.cookies || "";
let admin = false;
if (myCookie.token === process.env.TOKEN) {
admin = true;
}
const res = await axios.get("http://localhost:3000/api/products");
return {
props: {
pizzaList: res.data,
admin,
},
};
};
When i upload the application to vercel i get this error :
This is the error I'm getting in vercel server runtime
This is my /api/products/[id].js file code
import dbConnect from "../../../util/mongo";
import Product from "../../../models/Product";
export default async function handler(req, res) {
const {
method,
query: { id },
cookies,
} = req;
const token = cookies.token;
await dbConnect();
if (method === "GET") {
try {
const product = await Product.findById(id);
res.status(200).json(product);
} catch (err) {
console.log(err.response);
res.status(500).json(err);
}
}
if (method === "PUT") {
if (!token || token !== process.env.token) {
return res.status(401).json("Not authenticated!");
}
try {
const product = await Product.findByIdAndUpdate(id, req.body, {
new: true,
});
res.status(200).json(product);
} catch (err) {
console.log(err.response);
res.status(500).json(err);
}
}
if (method === "DELETE") {
if (!token || token !== process.env.token) {
return res.status(401).json("Not authenticated!");
}
try {
await Product.findByIdAndDelete(id);
res.status(200).json("The product has been deleted!");
} catch (err) {
console.log(err.response);
res.status(500).json(err);
}
}
}
This is my /api/products/index.js file code
import dbConnect from "../../../util/mongo";
import Product from "../../../models/Product";
export default async function handler(req, res) {
const { method, cookies } = req;
const token = cookies.token;
await dbConnect();
if (method === "GET") {
try {
const products = await Product.find();
res.status(200).json(products);
} catch (err) {
console.log(err.response);
res.status(500).json(err);
}
}
if (method === "POST") {
if (!token || token !== process.env.token) {
return res.status(401).json("Not authenticated!");
}
try {
const product = await Product.create(req.body);
res.status(201).json(product);
} catch (err) {
console.log(err.response);
res.status(500).json(err);
}
}
}
I suspect the error is coming from my get request but i cannot diagnose why. Anyone have any idea?
I am assuming the server side application can be set up as separate deployment. In that case, you can simply follow something similar to what is given here: https://vercel.com/guides/handling-node-request-body
Instead of sending a request to http://localhost:3000/api/products, you can use the deployment url for the server side code like http://some-deployment-url/api/products to access the data you require.
Related
I have this a Dashboard component which makes three API calls to fetch widget data.
If any API call fails it refreshes token.
But, when Dashboard renders it makes API call individually and they don't wait to check if first api call failed or token is refreshed. Each api call ends up making another call to refresh the token.
It should stop at first API call fail and refresh the token.
But it does so for each request. How can I prevent this behaviour.
It seems I need to make request sequentially.
const Dashboard = () => {
const { response: studentResponse } = useAxios(ApiConfig.STUDENT.GET_STUDENTS);
const { response: courseResponse } = useAxios(ApiConfig.COURSE.GET_COURSES);
const { response: feesResponse } = useAxios(ApiConfig.FEES.GET_TOTAL);
return (
<Box padding={2} width="100%">
<Stack direction={'row'} justifyContent="space-between" gap={2} mb={10}>
<NewWidget type={'student'} counter={studentResponse?.data?.length} />
<NewWidget type={'course'} counter={courseResponse?.data?.length} />
<NewWidget type={'earning'} counter={feesResponse?.data} />
</Stack>
</Box>
);
};
export default Dashboard;
use-axios.js
import { useState, useEffect } from 'react';
import axios from 'axios';
import history from '../utils/history';
import refreshToken from './refresh-token';
const Client = axios.create();
Client.defaults.baseURL = 'http://localhost:3000/api/v1';
const getUser = () => {
const user = localStorage.getItem('user');
return user ? JSON.parse(user) : null;
};
const updateLocalStorageAccessToken = (accessToken) => {
const user = getUser();
user.accessToken = accessToken;
localStorage.setItem('user', JSON.stringify(user));
};
Client.interceptors.request.use(
(config) => {
const user = getUser();
config.headers.Authorization = user?.accessToken;
return config;
},
(error) =>
// Do something with request error
Promise.reject(error)
);
Client.interceptors.response.use(
(response) => response,
(error) => {
// Reject promise if usual error
if (error.response.status !== 401) {
return Promise.reject(error);
}
const user = getUser();
const status = error.response ? error.response.status : null;
const originalRequest = error.config;
console.log(originalRequest);
if (status === 401 && originalRequest.url !== '/auth/refresh-token') {
refreshToken(user.refreshToken)
.then((res) => {
const { accessToken } = res.data.data;
Client.defaults.headers.common.Authorization = accessToken;
// update local storage
updateLocalStorageAccessToken(accessToken);
return Client(originalRequest);
})
.catch((err) => {
console.log(err);
if (err.response.status === 401) {
localStorage.setItem('user', null);
history.push('/login');
}
return Promise.reject(err);
});
}
history.push('/login');
return Promise.reject(error);
}
);
export const useAxios = (axiosParams, isAuto = true) => {
const [response, setResponse] = useState(undefined);
const [error, setError] = useState('');
const [loading, setLoading] = useState(true);
const fetchData = async (params) => {
try {
const result = await Client.request({
...params,
method: params.method || 'GET',
headers: {
accept: 'application/json',
},
});
setResponse(result.data);
} catch (error) {
setError(error);
} finally {
setLoading(false);
}
};
useEffect(() => {
if (isAuto) fetchData(axiosParams);
}, [axiosParams, isAuto]); // execute once only
return { fetch: () => fetchData(axiosParams), response, error, loading };
};
In the interceptor for the response, you check if there's an error. I would keep a state which contains the previous success of a call, implement that how you wish - after that, create an interceptor for requests which checks if that error occurred, and if so, cancel the request:
axios.interceptors.request.use((req: AxiosRequestConfig) => {
if(error){
throw new axios.Cancel('Operation canceled due to previous failure.');
}
else {
return req
}
})
Also see: Axios: how to cancel request inside request interceptor properly?
so I want to unprotect the login page and here is my folder structure:
here is my middleware:
import { NextResponse, NextRequest } from "next/server";
export async function middleware(req, ev) {
if (req.pathname === "login") {
return NextResponse.next();
}
const token = req.cookies.token;
if (!token) {
return NextResponse.redirect("/login");
}
return NextResponse.next();
}
so how do I make it so the middleware does not apply to login.js.
Edit: it now returns [webpack.cache.PackFileCacheStrategy] Caching failed for pack: Error: Unable to snapshot resolve dependencies
code for this project is here
so I solved the error I was not getting the pathname from req.nextUrl and here is the correct code:
import { NextResponse, NextRequest } from "next/server";
export async function middleware(req, ev) {
const { pathname } = req.nextUrl;
const token = req.cookies.token;
if (pathname == "/login" && !token) {
return NextResponse.next();
}
if (!token) {
return NextResponse.redirect("/login");
}
return NextResponse.next();
}
You can check if the request is for the /login page itself, and bail early.
Checkout all properties on NextRequest that are available.
import { NextResponse, NextRequest } from "next/server";
export async function middleware(req, ev) {
if( req.pathname === 'login'){
return NextResponse.next();
}
const token = req.cookies.token;
if (!token) {
return NextResponse.redirect("/login");
}
return NextResponse.next();
}
The Accepted answer helped me a lot. I'm adding my solution because I had to make some modifications.
I used [...nextauth].js file with jwt and session callbacks and when I used the solution from accepted answer, I was getting ERR_TOO_MANY_REQUESTS cycle. My solution was to add a few more URLs:
import { NextResponse } from "next/server";
import { getToken } from 'next-auth/jwt';
export default async function middleware(req, ev) {
const { pathname } = req.nextUrl;
const token = await getToken({ req, secret: process.env.JWT_SECRET });
if ((pathname == "/api/auth/signin" || pathname == "/favicon.ico" || pathname == "/api/auth/callback/credentials") && !token) {
return NextResponse.next();
}
if (!token) {
return NextResponse.rewrite(new URL('/api/auth/signin', req.url));
}
return NextResponse.next();
}
I did follow a tutorial of how to integrate mailchimp with node backend. I have never touched back end, so am pretty lame at it.
When I POST to their API I get the subscriber's credentials, but I get an error back - "Assignment to constant variable". Reading through the web and other SO questions, it seems like I am trying to reassign a CONST value.
I had a goooooooooood look at my code and the only thing I have noticed that might be issues here is
request(options, (error, response, body) => {
try {
const resObj = {};
if (response.statusCode == 200) {
resObj = {
success: `Subscibed using ${email}`,
message: JSON.parse(response.body),
};
} else {
resObj = {
error: ` Error trying to subscribe ${email}. Please, try again`,
message: JSON.parse(response.body),
};
}
res.send(respObj);
} catch (err) {
const respErrorObj = {
error: " There was an error with your request",
message: err.message,
};
res.send(respErrorObj);
}
});
I have noticed I am creating an empty object called "resObj", then trying to assign a value to it.
I have tried changing the CONST to LET, but I get an error saying: "resObj is not defined".
Here is my front end code:
import React, { useState } from "react";
import "./App.css";
import Subscribe from "./components/Subscribe";
import Loading from "./components/Loading/Loading";
import axios from "axios";
import apiUrl from "./helpers/apiUrl";
function App() {
const [loading, setLoading] = useState(false);
const [email, setEmail] = useState("");
const handleSendEmail = (e) => {
setLoading(true);
console.log(email);
axios
.post(`${apiUrl}/subscribe`, { email: email })
.then((res) => {
if (res.data.success) {
alert(`You have successfully subscribed!, ${res.data.success}`);
setEmail("");
setLoading(false);
} else {
alert(`Unable to subscribe, ${res.data.error}`);
console.log(res);
setLoading(false);
setEmail("");
}
})
.catch((err) => {
setLoading(false);
alert("Oops, something went wrong...");
console.log(err);
setEmail("");
});
e.preventDefault();
};
const handleInput = (event) => {
setEmail(event.target.value);
};
// const handleLoadingState = (isLoading) => {
// setLoading({ isLoading: loading });
// console.log(loading);
// };
return (
<div className='App'>
<h1>Subscribe for offers and discounts</h1>
{loading ? (
<Loading message='Working on it...' />
) : (
<Subscribe
buttonText='Subscribe'
value={email}
handleOnChange={handleInput}
handleOnSubmit={handleSendEmail}
/>
)}
</div>
);
}
export default App;
And the Back end code:
const restify = require("restify");
const server = restify.createServer();
const corsMiddleware = require("restify-cors-middleware");
const request = require("request");
require("dotenv").config({ path: __dirname + "/variables.env" });
const subscribe = (req, res, next) => {
const email = req.body.email;
const dataCenter = process.env.DATA_CENTER;
const apiKey = process.env.MAILCHIMP_API_KEY;
const listID = process.env.LIST_ID;
const options = {
url: `https://${dataCenter}.api.mailchimp.com/3.0/lists/${listID}/members`,
method: "POST",
headers: {
"content-type": "application/json",
Authorization: `apikey ${apiKey}`,
},
body: JSON.stringify({ email_address: email, status: "subscribed" }),
};
request(options, (error, response, body) => {
try {
const resObj = {};
if (response.statusCode == 200) {
resObj = {
success: `Subscibed using ${email}`,
message: JSON.parse(response.body),
};
} else {
resObj = {
error: ` Error trying to subscribe ${email}. Please, try again`,
message: JSON.parse(response.body),
};
}
res.send(respObj);
} catch (err) {
const respErrorObj = {
error: " There was an error with your request",
message: err.message,
};
res.send(respErrorObj);
}
});
next();
};
const cors = corsMiddleware({
origins: ["http://localhost:3001"],
});
server.pre(cors.preflight);
server.use(restify.plugins.bodyParser());
server.use(cors.actual);
server.post("/subscribe", subscribe);
server.listen(8080, () => {
console.log("%s listening at %s", server.name, server.url);
});
If anyone could help I would be very grateful. The subscription form works, but I need to clear that bug in order for my front end to work correctly onto submission of the form.
Maybe what you are looking for is Object.assign(resObj, { whatyouwant: value} )
This way you do not reassign resObj reference (which cannot be reassigned since resObj is const), but just change its properties.
Reference at MDN website
Edit: moreover, instead of res.send(respObj) you should write res.send(resObj), it's just a typo
i am curently stuck in trying to set up an API on a react application. I did test my access token manually and it worked
https://api.instagram.com/v1/users/self/media/recent/?access_token=${accessToken}
but i am stuck when its about to import theses data into the statement of the app.
I check the architecture of the component adding manually url into the state and it worked out, so i get the probleme is probably in the function that i called diplayInsta() or in the util/insta.js method
const Insta = {
getAccessToken() {
//check if token exist
if (accessToken) {
return new Promise(resolve => resolve(accessToken));
}
//token ref
return fetch(`https://api.instagram.com/oauth/authorize/? client_id=${client_id}&redirect_uri=${redirectURI}&response_type=token`, {
method: 'POST'
}).then(response => {
return response.json();
}).then(jsonResponse => {
accessToken = jsonResponse.access_token;
});
},
async display() {
if (!accessToken) {
this.getAccessToken();
}
try {
let response = await fetch(`https://api.instagram.com/v1/users/self/media/recent/?access_token=${accessToken}`, {
method: 'GET',
headers: {
Authorization: `Bearer ${accessToken}`
}
});
if (response.ok) {
let jsonResponse = await response.json();
let medias = jsonResponse.medias.data.map(media => ({
id: media.data.id,
image: media.data.images.standard_resolution.url
}));
return medias;
}
throw new Error('Request failed!');
} catch (error) {
console.log(error);
}
}
}
Here is my github link of the project where you will found the whole project. https://github.com/erwanriou/reactisbroken
I feel that its probably a very small mistake but i can't found where it is...I a good soul could help me to show me the good direction...
UPDATE - After the first answer i did actualise the code and i now resolve the access token part but still stuck in displaying it into the state of the App.js That is my major problem curently
here a screen of the working promise :
https://www.dropbox.com/s/w9wh3h3m58r3n06/Promise.jpg?dl=0
Ok i found myself the solution. i put it here in case some of you are block in a similar problem :
let accessToken;
const Insta = {
getAccessToken() {
if (accessToken) {
return new Promise(resolve => resolve(accessToken));
}
const accessTokenMatch = window.location.href.match(/access_token=([^&]*)/);
if (accessTokenMatch) {
accessToken = accessTokenMatch[1];
return accessToken;
} else {
const Url = `https://api.instagram.com/oauth/authorize/?client_id=${client_id}&redirect_uri=${redirectURI}&response_type=token`
window.location = Url;
}
},
async display() {
if (!accessToken) {
this.getAccessToken();
}
try {
let response = await fetch(`https://api.instagram.com/v1/users/self/media/recent/?access_token=${accessToken}`, {
method: 'GET'
});
if (response.ok) {
console.log(response);
let jsonResponse = await response.json();
let medias = jsonResponse.data.map(media => ({
id: media.id,
image: media.images.standard_resolution.url
}));
return medias;
}
throw new Error('Request failed!');
} catch (error) {
console.log(error);
}
}
}
Now the part to import the fetch data into the state is :
import React, { Component } from 'react';
import './App.css';
import Header from '../Header/Header.js';
import MediaList from '../MediaList/MediaList.js';
import Insta from '../../util/insta.js';
class App extends Component {
constructor(props) {
super(props);
this.state = {
mediasResult: [],
accountName: 'Erwan'
};
}
componentDidMount() {
Insta.display().then(medias => this.setState({mediasResult: medias}));
}
render() {
return (
<div className="App">
<Header accountName={this.state.accountName}/>
<MediaList medias={this.state.mediasResult}/>
</div>
);
}
}
export default App;
In fact the componentDidMount save my day. Its that let you import fetch data into the state.
In my vue.js application I've a login system. My main.js looks like this:
import Vue from 'vue';
import NProgress from 'nprogress';
import Resource from 'vue-resource';
import Router from 'vue-router';
import App from './App.vue';
import Login from './components/Authentication/Login.vue';
import auth from './Services/Authentication/Auth';
Vue.use(Router);
Vue.use(Resource);
auth.checkAuth();
export var router = new Router({
history: true
});
router.map({
'/': {
name: 'Login',
component: Login,
guest: true
}
});
router.beforeEach((transition) => {
if (transition.to.auth && !auth.user.authenticated) {
transition.redirect('/login');
} else if (transition.to.guest && auth.user.authenticated) {
transition.redirect('/');
} else {
transition.next();
}
});
Vue.http.interceptors.push((request, next) => {
NProgress.start();
const token = auth.getToken();
request.headers['Authorization'] = 'Bearer ' + token;
request.headers['X-CSRF-TOKEN'] = document.querySelector('meta[name="token"]').content;
request.respondWith(request.body);
next((response) => {
NProgress.done();
if (response.status == 404) {
router.go('/');
} else if (response.status == 401 && response.data.refreshed_token) {
// If you received 401 "Unauthorized" response
// with a refreshed_token in the payload,
// this means you've got to refresh your token
auth.setToken(response.data.refreshed_token);
}
return response;
});
});
So on every request I check the user auth.checkAuth(); that function looks like this (Auth.js):
checkAuth () {
var token || localStorage.getItem('jwt-token');
if (!token) {
return false;
}
Vue.http.get('/api/me')
.then(({data}) => {
this.user.id = data.id;
this.user.name = data.name;
this.user.email = data.email;
this.user.role = data.role;
this.user.authenticated = true;
}, ({data}) => {
router.go('/');
});
}
So my problem is that in my router.beforeEach -> auth.user.authenticated I check if the user is authenticated. But because the promise from auth.checkAuth(); is not returned so auth.user.authenticated is always false!
How can I fix this problem?
Would be very helpful!
For future users having the same problem
Vue.http.interceptors.push((request, next) => {
request.headers.set('X-CSRF-TOKEN', Laravel.csrfToken);
next((response) => {
if(response.status == 401 ) {//or add any error code you want here
//Do another request for some endpoint here to get a fresh token and override your token variable
}
});
});