NextJS use client-side error in custom error page - reactjs

NextJS renders the custom error page (_error.jsx) when there is client-side UI error.
Is it possible to get information on the error inside the custom error page?
I'm using a custom App:
// pages/_app.jsx
import App from 'next/app'
function CustomApp({ Component, pageProps }) {
return <Component {...pageProps} />
}
CustomApp.getInitialProps = async (props) => {
{ Component, ctx } = props
const initProps = await Component.getInitialProps(ctx);
return { pageProps: initProps }
}
export default CustomApp
and I have a page which triggers an error
// pages/throw_error.jsx
function ThrowErrorPage() {
throw new Error("client-side error")
...
}
export default ThrowError
and a custom error page
// pages/_error.jsx
function Error(props) {
// Expecting error from the UI
const { err } = props;
return (
<div>{err.message}</div>
)
}
Error.getInitialProps = async (props) => {
const { err } = props;
const statusCode = res ? res.statusCode : err ? err.statusCode : 404;
return { err, statusCode };
}
export default Error
I was expecting err to be passed from the UI to _error.jsx. Am I missing something?

So it turns out that NextJS does return client-side error to the custom error component. When a client-side error occurs, NextJS fetches the custom _error.jsx [1], and passes it as the value of the Component to the custom App (_app.jsx), along with a ctx object which contains the error object err [2]. In the custom App, we can then pass the error object to the error component as a prop, and extract it in the component for display/logging:
First pass the error object in the custom app to the component where the error is going to be rendered:
// pages/_app.jsx
import App from 'next/app'
function CustomApp({ Component, pageProps }) {
return <Component {...pageProps} />
}
CustomApp.getInitialProps = async (props) => {
{ Component, ctx : { err } } = props;
// Return early if there is an error
// pass the error to the component
if (err) return { pageProps : { err } };
const componentProps = await Component.getInitialProps(ctx);
return { pageProps: { ...componentProps } };
}
export default CustomApp;
Then extract the error object from the component's prop
// pages/_error.jsx
function Error({ err }) {
logToService(err)
return err.message;
}
export default Error;

Related

Nextjs not server side rendered when i wrapped component by auth

I using Next.js for SSR, after I try to make authentication for user client by Auth component following to my idea. But I have a problem when View Source page, it not loaded as HTML tag which empty root like create-react-app, now it my code in _app.js:
function MyApp({ Component, pageProps: { ...pageProps } }) {
const { auth } = Component;
return (
<>
{
auth ?
<Auth>
<Component {...pageProps} />
</Auth>
:
<Component {...pageProps} />
}
</>
)
}
Which Auth component is:
export const Auth = async (props) => {
const { children } = props;
const auth = await POST('http://localhost:7000/services/api/auth');
if (!auth) {
return <div>Loading...</div>
}
return children
}
And the page wrapped by auth component is:
export const getServerSideProps = async ctx =>{
const { id } = ctx.query;
try {
const postById = await GET(`http://localhost:7000/services/api/post/${id}`);
return {
props: {
post: postById
},
}
}
catch (err) {
console.log(err)
}
}
export default const DetailPost = (props) => {
return (
...
)
}
What should I do it for rendered with HTML DOM like server-side-rendering?

Next.js getting undefined in console for async request

Getting undefined for the following code.
home.js
export async function getServerSideProps() {
// Fetch data from external API
const res = await fetch(`https://jsonplaceholder.typicode.com/users`)
const data = await res.json()
// Pass data to the page via props
return {
props: {
data,
},
}
}
const Home = ({
data
}) => {
console.log(data)
return ( <
div > Hello < /div>
)
}
export default Home;
_app.js
function MyApp({ Component, pageProps }) {
return <Component {...pageProps} />
}
export default MyApp
I found a recent answer where it was mentioned that issue may be because of _app.js but i am not able to get the value in console.

React hook useContext hook does not want to work

Im trying to achive sign-in functionality and pass user info to components tree using React Context:
// AuthProvider.tsx
import React, { createContext, useContext, useState, useEffect } from 'react';
import { useRouter } from 'next/router';
import router from 'next/router';
type User = {
name?: string
error: boolean
}
const AuthContext = createContext<User>({} as User);
function AuthProvider({ children }: any): JSX.Element {
const { pathname, events } = useRouter();
const [user, setUser] = useState<User | null>({} as User);
// called from effects
async function getUser() {
try {
const response = await fetch('/api/auth/userinfo');
const profile: User = await response.json();
// if server send error field (no token or something...)
if (profile.error) {
setUser(null); // at this moment my backend always respond with
// res.send({name: "Babushka", error: false}) - so no truthly error field here
} else {
setUser(profile as User);
console.log('profile: ', profile); // i see {name: "Babushka"}
// in Chrome console...
}
} catch (err) {
console.error('Error: ', err)
}
}
// call getUser on route change
useEffect(() => {
getUser()
}, [pathname])
// call from effect
const handleChangeRoute = (url: string) => {
if (url === '/restricted' && !user) {
router.push('/login')
}
}
useEffect(() => {
// check if initial route is restricted
if (pathname === '/restricted' && !user) {
router.push('/login')
}
// subscribe for Nextjs router event
events.on('routeChangeStart', handleChangeRoute)
// unsubscripe
return () => {
events.off('routeChangeStart', handleChangeRoute)
}
}, [user])
return (
<AuthContext.Provider value={{user: user}} > // TS Error
{ user && user?.name } // i see Babushka here rendered too ...
{ children }
</ AuthContext.Provider>
)
}
const useAuth = () => useContext(AuthContext)
export { AuthProvider, useAuth }
For this demo purposes, I'm just want to test AuthContext for access user info in wrapped components, so i mock my api backend to send only this object {name: "Babushka", error: false} to make getUser result always be a non-empty object.
But next on my restricted page, where wrapped component with AuthProvider, I can't get user.
import { GetStaticProps } from 'next';
import { Group } from '../../types/Groups';
import {AuthProvider, useAuth} from '../../components/AuthProvider';
export default function RestrictedPage({ ...props }) {
const {...user} = useAuth() // trying to destructure user info from Provider value, but getting always empty object {}
return (
<AuthProvider>
<MainPageWrapper>
<h1>Restricted area</h1>
<p>{user && user.name}</p> // no render
</MainPageWrapper>
</AuthProvider>
)
}
Do you see anything wrong with this code? Why i can get object passed from backend api, and it's dont affect on hooks ? And another question - TS Error in AuthContext says: *Type '{ user: User | null; }' is not assignable to type 'User'.
Object literal may only specify known properties, and 'user' does not exist in type 'User'.*
This is obviously not a reason why hooks won't working, but i can't get why this error occurs. Glad to any help!

NextJS: loading json into getInitialProps in _app.js vs pages and avoiding double calling

I've used getInitialProps to load a large json file onto an individual page. Since it's a rather large json, I was wondering how I should go about loading it onto the index page to start with. The subpages should load it only if someone goes onto a subpage directly bypassing the index and it's not in the props already. The docs are a bit confusing on loading onto the _app compared to individual components. Also, not clear how to do a check within then getInitialProps if the props are already fetched...
import App from 'next/app'
import React from 'react'
import withReduxStore from '../store/with-redux-store'
import { Provider } from 'react-redux'
import "isomorphic-fetch"
class MyApp extends App {
static async getInitialProps({ Component, ctx }) {
let res = await fetch('https://xxxx.json', { mode: 'no-cors' });
let productParams = await res.text().then((data) => {
return (data ? JSON.parse(data) : {})
})
.catch((error) => {
console.error("Something bad happened", error);
});
console.log(`Show data fetched. Count: ${Object.keys(productParams).length}`);
return { productParams, topState: "loaded" }
}
render() {
return (
<Provider store={reduxStore}>
<Component {...this.props} />
</Provider>
)
}
}
export default withReduxStore(MyApp)
________________________________________________
class SubPage extends React.Component {
static async getInitialProps({ reduxStore, topState }) {
reduxStore.dispatch(loadInitialState());
if (topState != "loaded") {
let res = await fetch('https://xxxxxx.json', { mode: 'no-cors' })
let productParams = await res.json();
return { productParams }
} else {
return {}
}
}
state = { ...this.props, riskType: "xxx" }
componentDidMount() {
console.log(this.state);
}
render() {
return (
<Layout>
<SubComponent />
</Layout>
)
}
}
const mapStateToProps = (state) => {
return state;
};
const mapDispatchToProps = (dispatch) => {
return {
loadInitialState: () => {
dispatch({ type: "LOAD_INITIAL_STATE" });
}
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(SubPage)
If I go to the main page, the _app loads the json, then if I click on the subpage link, its no longer in the props. Only when I reload the subpage, it appears in the props again. What am I doing wrong?
From what I can gather, your example seems to have several issues. But the main ones that would need addressing for your scenario to work are:
Component on _app.js is actually a prop passed to your MyApp component.
Since you are overriding it, you should call App.getInitialProps inside your static MyApp.getInitialProps. That would actually trigger calls to your page's own getInitialProps.
The props returned from getInitialProps in _app.js are sent as pageProps to MyApp.
Putting it all together would look something like this:
import App from 'next/app'
function MyApp({ Component, pageProps }) {
return <Component {...pageProps} />
}
MyApp.getInitialProps = async (appContext) => {
// calls page's `getInitialProps` and fills `appProps.pageProps`
const appProps = await App.getInitialProps(appContext);
// Fetch your json file
const res = await fetch('https://xxxx.json', { mode: 'no-cors' });
const productParams = await res.json();
return { ...appProps, productParams, topState: "loaded" };
}
export default MyApp
Just bear in mind that setting a getInitialProps from your custom App will force every page on your app to be server side rendered and void static optimization, entirely. You can read more about custom App on its official documentation section.

How do I render a 404 page in NextJS in development and production?

I have a situation where I am using nextjs for client-side and the '404 - page not found error' only appears in local development, but I would like to see it on dev and on production.
How can I archive this?
Found some examples on the net that worked well locally but didn't work again on dev and prod since I could get the statusCode locally from ctx.res.statusCode from the getInitialProps method in _app.js and display the default nextjs Error component if the status code is 404.
I also created a custom _errors.js page, but this also works in local dev only
Reusing the built-in error page :
import App, { Container } from 'next/app';
import { ApolloProvider } from 'react-apollo';
import Error from 'next/error';
import Page from '../components/Page';
import withData from '../lib/withData';
class MyApp extends App {
static async getInitialProps({ Component, ctx }) {
let pageProps = {};
if (Component.getInitialProps) {
pageProps = await Component.getInitialProps(ctx);
}
const { res } = ctx;
const errorCode = res.statusCode > 200 ? res.statusCode : false;
pageProps.query = ctx.query;
return {
pageProps,
errorCode,
};
}
render() {
const { Component, apollo, pageProps, errorCode } = this.props;
if (errorCode) {
return <Error statusCode={errorCode} />;
}
return (
<Container>
<ApolloProvider client={apollo}>
<Page>
<Component {...pageProps} />
</Page>
</ApolloProvider>
</Container>
);
}
}
export default withData(MyApp);
Custom error handling solution :
import React from 'react';
import PropTypes from 'prop-types';
class Error extends React.Component {
static getInitialProps({ res, err }) {
let errCode = null;
if (res.statusCode) {
errCode = res.statusCode;
} else if (err.statusCode) {
errCode = err.statusCode;
}
return { errCode };
}
render() {
const { errCode } = this.props;
return <p>{errCode ? `An error ${errCode} occurred on server` : 'An error occurred on client'}</p>;
}
}
Error.propTypes = {
errCode: PropTypes.number,
};
export default Error;

Resources