Nextjs: custom error page with correct status code - javascript

I followed the next docs regarding a custom error page. I want to reuse the error page if certain errors occur at getStaticProps. Something like:
const Page: NextPage<Props> = ({ pageData, error }: Props) => {
if (error) return <Error statusCode={500} />;
return <Page data={pageData} />;
};
export const getStaticProps: GetStaticProps = async ({
params,
}: {
params: { [prop: string]: string[] };
}) => {
const { slug } = params;
const {pageData, error} = getPageData(slug)
return {
props: {
pageData: page || null,
error: error || null,
},
};
};
export default Page;
The error page is just like in the docs:
function Error({ statusCode }) {
return (
<p>
{statusCode ? `An error ${statusCode} occurred on server` : 'An error occurred on client'}
</p>
);
}
Error.getInitialProps = ({ res, err }) => {
const statusCode = res ? res.statusCode : err ? err.statusCode : 404;
return { statusCode };
};
export default Error;
This works but the status code ist wrong. Nextjs still answers a request to this page with a status code 200. I need it to set the status code to 500 as if there was a server error.

you can change your page status code if you are using "getServerSideProps" or "getStaticProps", you only need to change the response status code before returning the page props:
const HomePage = ({pageProps}) => {
if (pageProps.hasError) {
return <CustomError status={error.status} message={error.message}/>
}
return <div>This is home page </div>
}
export const getServerSideProps = async ({req,res}) => {
try {
const homeData = await fetchHomeData()
return {
props: {
data: homeData
}
}
} catch (err) {
res.statusCode = exception.response.status /* this is the key part */
return {
props: {
hasError: true, error: {
status: exception.response.status || 500,
message: exception.message
}
}
}
}
}

Related

Where to set Sentry's setUser in Next.js app?

I have been trying to set user data into Sentry's scope globally, so every time there's an error or event, user info is passed to it.
My app is built in Next.js, so naturally I added the config as it is in Sentry's documentation for Next.js.
I haven't got the idea on where to add the Sentry.setUser({id: user.Id}) method in order for it to set the user globally.
So far I have added it to the Sentry's _error.js file, inside the getInitialProps method:
import NextErrorComponent from 'next/error';
import * as Sentry from '#sentry/nextjs';
import { getUser } from '../lib/session';
const MyError = ({ statusCode, hasGetInitialPropsRun, err }) => {
if (!hasGetInitialPropsRun && err) {
Sentry.captureException(err);
}
return <NextErrorComponent statusCode={statusCode} />;
};
MyError.getInitialProps = async (context) => {
const errorInitialProps = await NextErrorComponent.getInitialProps(context);
const { req, res, err, asPath } = context;
errorInitialProps.hasGetInitialPropsRun = true;
const user = await getUser(req, res);
// Set user information
if (user) {
console.log('Setting user');
Sentry.setUser({ id: user.Id });
}
else {
console.log('Removing user');
Sentry.configureScope(scope => scope.setUser(null));
}
if (res?.statusCode === 404) {
return errorInitialProps;
}
if (err) {
Sentry.captureException(err);
await Sentry.flush(2000);
return errorInitialProps;
}
Sentry.captureException(
new Error(`_error.js getInitialProps missing data at path: ${asPath}`),
);
await Sentry.flush(2000);
return errorInitialProps;
};
export default MyError;
But when trying to log errors, the user info doesn't show in Sentry, only the default user ip:
I have also tried setting the user after successful login, and still nothing..
Help is appreciated!!
Not sure if this is the right way, but the above solutions didn't work for me. So I tried calling setUser inside _app.tsx.
import { useEffect } from "react";
import { setUser } from "#sentry/nextjs";
import { UserProvider, useUser } from "#auth0/nextjs-auth0";
import type { AppProps } from "next/app";
function SentryUserManager() {
const { user } = useUser();
useEffect(() => {
if (user) {
setUser({
email: user.email ?? undefined,
username: user.name ?? undefined,
});
} else {
setUser(null);
}
}, [user]);
return null;
}
export default function MyApp({ Component, pageProps }: AppProps) {
return (
<UserProvider>
<Component {...pageProps} />
<SentryUserManager />
</UserProvider>
);
}
Still not sure why this worked for me and the other solutions didn't, but figured it was worth sharing.
I would suggest using the callback handler to set your Sentry user context.
import { handleAuth, handleLogin, handleCallback } from "#auth0/nextjs-auth0";
import * as Sentry from "#sentry/nextjs";
import { NextApiHandler } from "next";
const afterCallback = (_req, _res, session, _state) => {
Sentry.setUser({
id: session.user.sub,
email: session.user.email,
username: session.user.nickname,
name: session.user.name,
avatar: session.user.picture,
});
return session;
};
const handler: NextApiHandler = handleAuth({
async login(req, res) {
await handleLogin(req, res, {
returnTo: "/dashboard",
});
},
async callback(req, res) {
try {
await handleCallback(req, res, { afterCallback });
} catch (error) {
res.status(error.status || 500).end(error.message);
}
},
});
export default Sentry.withSentry(handler);
You can set the user in Sentry right after successful login
const handleLogin = {
try {
const res = await axios.post("/login", {"john#example.com", "password"})
if (res && res?.data) {
// Do other stuff
Sentry.setUser({ email: "john#example.com" });
}
}
}
Additionaly you can clear the user while logging out
const handleLogout = {
// Do othe stuff
Sentry.configureScope(scope => scope.setUser(null));
}

How to redirect user to notFound Page in getServerSideProps whenever an invalid params.id is entered in Next.JS

export const getServerSideProps = async ({ params }) => {
const res = await fetch(
`https://pixabay.com/api/?key=${process.env.API_KEY}&id=${params.id}`
);
const { hits } = await res.json();
if (!hits) {
return {
notFound: true,
};
}
return {
props: { pic: hits[0] },
notFound: true,
};
};
I keep getting this error whenever a user enter an invalid params.id=====>
Server Error
FetchError: invalid json response body at https://pixabay.com/api/?key=*************************************&id=**xzk** reason: Unexpected token E in JSON at position 1
I used "xzk" as the id value which is invalid and instead of redirecting user to notFoundPage, it keeps showing this error, and the error is even revealing my api-keys. Please what am I doing wrong? Ideas is also welcome. Thanks in advance.
I've found a solution to this, which is checking if the response is OK. And if it is not Ok then it should redirect the user to notFound page which was set to true in the if statement.
export const getServerSideProps = async ({ params }) => {
const { id } = params;
const res = await fetch(
`https://pixabay.com/api/?key=${process.env.API_KEY}&id=${id}`
);
if (!res.ok) {
return {
notFound: true,
};
}
const { hits } = await res.json();
return {
props: { pic: hits[0] },
};
};

Unsubscribe email using Fetch api Javascript

I have a form where i enter an email and it gets ''subscribed'' in a user.json file using a fetch api on node server.My task is to :
upon clicking on the "Unsubscribe" button, implement the functionality for unsubscribing from the community list. For that, make POST Ajax request using http://localhost:3000/unsubscribe endpoint.
I tried to make the function but it wasnt succeseful so i deleted it. Also,i need to do the following :
While the requests to http://localhost:3000/subscribe and
http://localhost:3000/unsubscribe endpoints are in progress, prevent
additional requests upon clicking on "Subscribe" and "Unsubscribe".
Also, disable them (use the disabled attribute) and style them using
opacity: 0.5.
For me ajax requests,fetch and javascript is something new,so i dont know really well how to do this task,if you could help me i'll be happy,thanks in advance.
fetch code for subscribing:
import { validateEmail } from './email-validator.js'
export const sendSubscribe = (emailInput) => {
const isValidEmail = validateEmail(emailInput)
if (isValidEmail === true) {
sendData(emailInput);
}
}
export const sendHttpRequest = (method, url, data) => {
return fetch(url, {
method: method,
body: JSON.stringify(data),
headers: data ? {
'Content-Type': 'application/json'
} : {}
}).then(response => {
if (response.status >= 400) {
return response.json().then(errResData => {
const error = new Error('Something went wrong!');
error.data = errResData;
throw error;
});
}
return response.json();
});
};
const sendData = (emailInput) => {
sendHttpRequest('POST', 'http://localhost:8080/subscribe', {
email: emailInput
}).then(responseData => {
return responseData
}).catch(err => {
console.log(err, err.data);
window.alert(err.data.error)
});
}
index.js from route node server:
const express = require('express');
const router = express.Router();
const FileStorage = require('../services/FileStorage');
/* POST /subscribe */
router.post('/subscribe', async function (req, res) {
try {
if (!req.body || !req.body.email) {
return res.status(400).json({ error: "Wrong payload" });
}
if (req.body.email === 'forbidden#gmail.com') {
return res.status(422).json({ error: "Email is already in use" });
}
const data = {email: req.body.email};
await FileStorage.writeFile('user.json', data);
await res.json({success: true})
} catch (e) {
console.log(e);
res.status(500).send('Internal error');
}
});
/* GET /unsubscribe */
router.post('/unsubscribe ', async function (req, res) {
try {
await FileStorage.deleteFile('user.json');
await FileStorage.writeFile('user-analytics.json', []);
await FileStorage.writeFile('performance-analytics.json', []);
await res.json({success: true})
} catch (e) {
console.log(e);
res.status(500).send('Internal error');
}
});
module.exports = router;
And user.json file looks like this :
{"email":"Email#gmail.com"}
This is my attempt for unsubscribing :
export const unsubscribeUser = () => {
try {
const response = fetch('http://localhost:8080/unsubscribe', {
method: "POST"
});
if (!response.ok) {
const message = 'Error with Status Code: ' + response.status;
throw new Error(message);
}
const data = response.json();
console.log(data);
} catch (error) {
console.log('Error: ' + error);
}
}
It gives the following errors:
Error: Error: Error with Status Code: undefined
main.js:2
main.js:2 POST http://localhost:8080/unsubscribe 404 (Not Found)
FileStorage.js:
const fs = require('fs');
const fsp = fs.promises;
class FileStorage {
static getRealPath(path) {
return `${global.appRoot}/storage/${path}`
}
static async checkFileExist(path, mode = fs.constants.F_OK) {
try {
await fsp.access(FileStorage.getRealPath(path), mode);
return true
} catch (e) {
return false
}
}
static async readFile(path) {
if (await FileStorage.checkFileExist(path)) {
return await fsp.readFile(FileStorage.getRealPath(path), 'utf-8');
} else {
throw new Error('File read error');
}
}
static async readJsonFile(path) {
const rawJson = await FileStorage.readFile(path);
try {
return JSON.parse(rawJson);
} catch (e) {
return {error: 'Non valid JSON in file content'};
}
}
static async writeFile(path, content) {
const preparedContent = typeof content !== 'string' && typeof content === 'object' ? JSON.stringify(content) : content;
return await fsp.writeFile(FileStorage.getRealPath(path), preparedContent);
}
static async deleteFile(path) {
if (!await FileStorage.checkFileExist(path, fs.constants.F_OK | fs.constants.W_OK)) {
return await fsp.unlink(FileStorage.getRealPath(path));
}
return true;
}
}
module.exports = FileStorage;
You should consider using a database for handling CRUD operations on your persisted data. If you must use filestorage, theres a flat file DB library called lowdb that can make working the files easier.
As for preventing duplicate requests, you can track if user has already made a request.
let fetchBtn = document.getElementById('fetch')
let isFetching = false
fetchBtn.addEventListener('click', handleClick)
async function handleClick(){
if (isFetching) return // do nothing if request already made
isFetching = true
disableBtn()
const response = await fetchMock()
isFetching = false
enableBtn()
}
function fetchMock(){
// const response = await fetch("https://example.com");
return new Promise(resolve => setTimeout (() => resolve('hello'), 2000))
}
function disableBtn(){
fetchBtn.setAttribute('disabled', 'disabled');
fetchBtn.style.opacity = "0.5"
}
function enableBtn(){
fetchBtn.removeAttribute('disabled');
fetchBtn.style.opacity = "1"
}
<button type="button" id="fetch">Fetch</button>

How to display error messages from django-rest-framework in React

I am trying to implement user Registration form using Django rest framework and react, redux. I am able to register user successfully, but I am facing issue in displaying error those are provided by Django in case of error.
What I have done so far
export const AUTH_START = 'AUTH_START';
export const AUTH_SUCCESS = 'AUTH_SUCCESS';
export const AUTH_FAIL = 'AUTH_FAIL';
export const AUTH_LOGOUT = 'AUTH_LOGOUT';
Here is Reducer functionality
const initialState = {
token: null,
error: null,
loading: false
}
const authFail = (state, action) => {
return updateObject(state, {
error: action.error,
loading: false
});
}
const reducer = (state = initialState, action) => {
switch (action.type) {
case actionTypes.AUTH_START:
return authStart(state, action);
case actionTypes.AUTH_SUCCESS:
return authSuccess(state, action);
case actionTypes.AUTH_FAIL:
return authFail(state, action);
case actionTypes.AUTH_LOGOUT:
return authLogout(state, action);
default:
return state;
}
}
export default reducer;
export const updateObject = (oldObject, updatedProperties) => {
return {
...oldObject,
...updatedProperties
}
}
Here is store functionality
export const authFail = (error) => {
return {
type: actionTypes.AUTH_FAIL,
error: error
}
}
export const authSignup = (username, email, password1, password2) => {
return dispatch => {
dispatch(authStart());
axios.post('http://127.0.0.1:8000/rest-auth/registration/', {
username: username,
email: email,
password1: password1,
password2: password2
}).then(res => {
const token = res.data.key;
const expirationDate = new Date(new Date().getTime() + 3600 * 1000);
localStorage.setItem('token', token);
localStorage.setItem('expirationDate', expirationDate);
dispatch(authSuccess(token));
dispatch(checkAuthTimeOut(3600));
}).catch(err => {
dispatch(authFail(err))
})
}
}
Here is settings.py
INSTALLED_APPS = [
'django.contrib.sites',
'allauth',
'allauth.account',
'allauth.socialaccount',
'corsheaders',
'rest_auth',
'rest_auth.registration',
'rest_framework',
'rest_framework.authtoken',
]
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.TokenAuthentication',
)
}
You can full error response from server like this
axios.get('/user/12345')
.catch(function (error) {
if (error.response) {
// The request was made and the server responded with a status code
// that falls out of the range of 2xx
console.log(error.response.data);
console.log(error.response.status);
console.log(error.response.headers);
} else if (error.request) {
// The request was made but no response was received
// `error.request` is an instance of XMLHttpRequest in the browser and an instance of
// http.ClientRequest in node.js
console.log(error.request);
} else {
// Something happened in setting up the request that triggered an Error
console.log('Error', error.message);
}
console.log(error.config);
});
So if you have error you can use dispatch to dispatch error something like this
dispatch(displayError(error.message));
dispatch(displayError(error.response.data));
dispatch(displayError(error.response.status));

How to catch the network error while fetching the error in promise

So I have this Picker component, and for the items, I am fetching the data from the server and mapping it when the state is changed.
//api.js
var api = {
getBooks() {
var url = 'http://192.168.43.14:8080/api/books/'
var headers = new Headers();
headers.append('Accept', 'application/json');
return fetch(url, headers)
.then((res) => res.json())
.catch(function(err) {
console.log('Fetch Error :-S', err);
throw Error(err);
});
}
}
//RegisterMobile.js
import api from '../utilities/api'
export default class RegisterMobile extends Component {
constructor(props) {
super(props)
this.state = {
books: [],
book: '',
mobile: '',
displayError: false,
error: 'Please provide a valid mobile number'
}
}
componentWillMount() {
api.getBooks().then((res) => {
this.setState({
books: res
})
})
}
render() {
return(
<View>
<View style={styles.selecBook}>
<Picker selectedValue={this.state.book} onValueChange={this.changeBook}>
{this.state.books.map((book) => {return <Picker.Item value={book.name} label={book.name} key={book.id} />})}
</Picker>
</View>
{this.state.displayError ? <Text style={styles.error}>{this.state.error}</Text> : null}
</View>
)
}
}
This is working fine. I get the list of items and when I click on the Picker, I can select the items. What I want to do is, if there was any error while fetching the data (eg. server is down), I would like to get that error and display it as error (which would be just changing the state of error and displayError). But I don't know how to get the error if there was one to set it in the state of the error.
Don't catch the error inside the api. Do it where you want to use the result of the async operation. Something like this...
//api.js
var api = {
getBooks() {
var url = 'http://192.168.43.14:8080/api/books/'
var headers = new Headers();
headers.append('Accept', 'application/json');
return fetch(url, headers)
.then((res) => res.json());
}
}
//RegisterMobile.js
import api from '../utilities/api'
export default class RegisterMobile extends Component {
constructor(props) {
super(props)
this.state = {
books: [],
book: '',
mobile: '',
displayError: false,
error: 'Please provide a valid mobile number'
}
}
componentWillMount() {
api.getBooks()
.then((res) => { this.setState({books: res}) })
.catch(function(err) {
console.log('Fetch Error :-S', err);
this.setState({books: [], book: null, displayError: true, error:err});
});
}
componentWillMount() {
api.getBooks()
.then((res) => { this.setState({books: res}) })
.catch(function(err) {
if (err.message === 'Network Error') {
// This is a network error.
}
});
}

Categories

Resources