catamphetamine / react-pages

A complete solution for building a React/Redux application: routing, page preloading, (optional) server-side rendering, asynchronous HTTP requests, document metadata, etc.
MIT License
176 stars 29 forks source link

Moving from isomorphic to website causes render service error #75

Closed russellbits closed 3 years ago

russellbits commented 5 years ago

I am upgrading a site currently running the react-isomorphic-render package to react-website. After changing all the reducers to the new react-website style (among other adjustments) I am now getting this not so helpful error in the console:

Render service is listening at port 3002
(node:25020) Warning: a promise was rejected with a non-error: [object Object]
(node:25020) Warning: a promise was rejected with a non-error: [object Object]
(node:25020) Warning: a promise was rejected with a non-error: [object Object]
(node:25020) Warning: a promise was rejected with a non-error: [object Object]
(node:25020) Warning: a promise was rejected with a non-error: [object Object]
[react-website] Rendering service error

Even more difficult is that web site itself simply appears as the word Error with nothing in the console.

One possible problem I see is that preload was a reducer function with the old package. Should that reducer just be eliminated now?

Alternately, since this appears to be a promise problem, could this have anything to do with the site using bluebird for promises?

catamphetamine commented 5 years ago

You could start with commenting out all @preload()s for a single route and then see if the error disappears. If it disappears then uncomment those @preload()s from top to bottom and see which one errors. Then comment it out and then uncomment it line-by-line to find the erroring piece of code.

russellbits commented 5 years ago

Thanks for the suggestion. I tried this—reducing the routes down to just the IndexRoute and commenting out any @preload function there but I still get the above errors. Incidentally the a promise was rejected with a non-error: [object Object] type errors do not show up until the page is reloaded in the browser. I tried shutting off javascript in the browser to get the server-side render to act alone. The whole HTML page just says "Error" with nothing in the console.

catamphetamine commented 5 years ago

@russellbits So it's not coming from a @preload() promise then... And it happens in the server-side part of the code.

If you look at the source code in the library it's this:

./node_modules/react-pages/commonjs/server/renderError.js

import { html } from 'print-error'

// Outputs an error page
export default function renderError(error, options)
{
    // If the error is caught here it means that `catch`
    // (error handler parameter) didn't resolve it
    // (or threw it)

    // Show error stack trace in development mode for easier debugging
    if (process.env.NODE_ENV !== 'production') {
        try {
            const { status = 500, content } = renderStackTrace(error, options)

            if (content) {
                return {
                    status,
                    content,
                    contentType: 'text/html'
                }
            }
        } catch (error) {
            console.log('[react-pages] Couldn\'t render error stack trace.')
            console.log(error.stack || error)
        }
    }

    // Log the error
    console.log('[react-pages] Rendering service error')
    // console.error(error)

    return {
        status: typeof error.status === 'number' ? error.status : 500,
        content: error.message || 'Error',
        contentType: 'text/plain'
    }
}

function renderStackTrace(error, options)
{
    // Supports custom `html` for an `Error`
    if (error.html) {
        return {
            status  : error.status,
            content : error.html
        }
    }

    // Handle `superagent` errors: if an error response was an html, then just render it
    // https://github.com/visionmedia/superagent/blob/29ca1fc938b974c6623d9040a044e39dfb272fed/lib/node/response.js#L106
    if (typeof error.status === 'number') {
        // If the `superagent` http request returned an html response
        // (possibly an error stack trace),
        // then just output that stack trace
        if (error.response
            && error.response.headers['content-type']
            && error.response.headers['content-type'].split(';')[0].trim() === 'text/html') {
            return {
                status  : error.status,
                content : error.message
            }
        }
    }

    // If this error has a stack trace then it can be shown
    const stack = getStackTrace(error)

    // If this error doesn't have a stack trace - do nothing
    if (!stack) {
        return {}
    }

    try {
        return {
            content : html({ stack }, options)
        }
    } catch (error) {
        console.error(error)
        return {
            content : error.stack
        }
    }
}

// Extracts stack trace from `Error`
function getStackTrace(error)
{
    // Standard javascript `Error` stack trace
    if (error.stack) {
        return error.stack
    }

    // `superagent` errors have the `original` property
    // for storing the initial error
    if (error.original && error.original.stack) {
        return error.original.stack
    }
}

You see there's a console.error() commented out there. You can uncomment it and see what it outputs.

russellbits commented 5 years ago

Thank you. I could not find that error line before. Now I get HttpError { status: 404, data: undefined } GET / 404 197.134 ms - 5

That's a bit more helpful.

russellbits commented 5 years ago

Does the @preload decorator have to use async/await? If it's not, could that be causing the Warning: a promise was rejected with a non-error: [object Object]

catamphetamine commented 5 years ago

Does the @preload decorator have to use async/await?

It's not clear what you mean.

russellbits commented 5 years ago

In your docs you mention "@preload() decorator takes an async/await function which takes an object of arguments," say, {preloadArugments}. I'm wondering if that always has to be the case.

catamphetamine commented 5 years ago

Yes. It doesn't say can take, it says takes.

russellbits commented 5 years ago

Do you recall if that was the case with the older package (react-isomorphic-render)?

catamphetamine commented 5 years ago

I guess it wasn't but no guarantees. There has been a lot of previous versions.

russellbits commented 5 years ago

Your server-side-render example does not use the same asynSettings that my app does.

import {underscoredToCamelCase} from 'react-website';

export default {
    // When supplying `event` instead of `events`
    // as part of an asynchronous Redux action
    // this will generate `events` from `event`
    // using this function.
    asynchronousActionEventNaming: (evt) => ([
        `${evt}_PENDING`,
        `${evt}_SUCCESS`,
        `${evt}_ERROR`
    ]),

    // When using `asynchronousActionHandler`
    // this function will generate a Redux state property name from an event name.
    // E.g. event `GET_USERS_ERROR` => state.`getUsersError`.
    asynchronousActionHandlerStatePropertyNaming: underscoredToCamelCase
};

This gets imported into my more typical react-website.js settings along with reducers. I'm not sure if this could be interfering with react-website's built-in events for preload...

catamphetamine commented 5 years ago

asyncSettings are no longer required. I'd suggest removing them from code.