Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[BUG] Reuse Server Correlation ID when Hydrating Error Pages. #846

Merged
merged 7 commits into from Dec 21, 2022

Conversation

bendvc
Copy link
Collaborator

@bendvc bendvc commented Dec 1, 2022

Description

Currently if an error happens on the server and an error page is rendered, you will get a React text mismatch error. This is the result of a new correlation id being generated on the client that doesn't match the error on the server.

To remedy this we need to be displaying the correlation id that is related to the request that caused an error to be rendered and not generating a new one.

Solution

To achieve the above desired outcome I've made the following changes:

  • Firstly we pass the current CID from the server to the client as a global called __INITIAL_CORRELATION_ID__
  • Secondly, we update the logic for updating the current CID in the clients CorrelationIdProvider component. Instead of always creating a new CID, we'll check to see if we are hydrating an Error page (we know this is true is there is a server error defined and its the first mount of the provider). If those conditions pass, we'll use the initial CID value.

Steps to Reproduce

  • On the test project, in query-error page, add an option to show error page
    const product = useProduct({id: '25502228Mxxx'}, {useErrorBoundary: true})
  • Start the app
  • Go to http://localhost:3000/query-errors
  • You should see the a warning in the console

Types of Changes

  • Bug fix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)
  • Documentation update
  • Breaking change (could cause existing functionality to not work as expected)
  • Other changes (non-breaking changes that does not fit any of the above)

Breaking changes include:

  • Removing a public function or component or prop
  • Adding a required argument to a function
  • Changing the data type of a function parameter or return value
  • Adding a new peer dependency to package.json

Changes

  • (change1)

How to Test-Drive This PR

  • (step1)

Checklists

General

  • Changes are covered by test cases
  • CHANGELOG.md updated with a short description of changes (not required for documentation updates)

Accessibility Compliance

You must check off all items in one of the follow two lists:

  • There are no changes to UI

or...

Localization

  • Changes include a UI text update in the Retail React App (which requires translation)

@bendvc bendvc requested a review from a team as a code owner December 1, 2022 19:50
@bendvc bendvc requested a review from alexvuong December 1, 2022 19:50
Comment on lines 45 to 48
if (isInitialPageRef.current && window.__ERROR__) {
isInitialPageRef.current = false
return window.__INITIAL_CORRELATION_ID__
}
Copy link
Collaborator

@bfeister bfeister Dec 1, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For my own eduction here, this (the useRef) is another one of our tricks to have various React lifecycle things be aware of first (SSR) vs. second (CSR) render passes?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wouldn't say this is something in the bag of tricks. The reason I used useRef here was because the alternative is using a global variable. The solution here is pretty much specific to the problem at hand, which is that we don't want to generated a new CSR correlation id when the thing we are rendering is an error page that happened on the server.

Copy link
Collaborator

@bfeister bfeister left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, minor question just to clarify my understanding of the problem

Copy link
Collaborator

@alexvuong alexvuong left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@bfeister
Copy link
Collaborator

bfeister commented Dec 8, 2022

@bendvc falling tests on this one 😔 tried to merge develop in for the circle changes but no dice

@alexvuong
Copy link
Collaborator

@@ -71,7 +71,7 @@ const OuterApp = ({routes, error, WrappedApp, locals}) => {
OuterApp.propTypes = {
routes: PropTypes.array.isRequired,
error: PropTypes.object,
WrappedApp: PropTypes.element.isRequired,
WrappedApp: PropTypes.func.isRequired,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So are we moving from WrappedApp being an element to being a SFC (stateless functional component)?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is a HOC actually, the wrappedApp is supposed to be a RouteComponent what is wrapped with some HOC.

Comment on lines +17 to +52
describe('main', function() {
test('OuterApp renders without error', () => {
uuidv4.mockReturnValueOnce('7f21aea5-6962-4162-8204-9da85c802022')
const oldPreloadedState = window.__PRELOADED_STATE__
window.__PRELOADED_STATE__ = {
appProps: {}
}
const App = () => <div>App</div>
const locals = {}
const props = {
error: undefined,
locals,
routes: getRoutes(locals),
WrappedApp: routeComponent(App, false, locals)
}
const wrapper = mount(<OuterApp {...props} />)
expect(wrapper.find(App).length).toBe(1)
window.__PRELOADED_STATE__ = oldPreloadedState
})

test('OuterApp triggers the error page when there is an error', () => {
const oldWindowError = window.__ERROR__
window.__ERROR__ = new errors.HTTPNotFound('Not found')
const App = () => <div>App</div>
const locals = {}
const props = {
error: window.__ERROR__,
locals,
routes: getRoutes(locals),
WrappedApp: routeComponent(App, false, locals)
}
const wrapper = mount(<OuterApp {...props} />)
expect(wrapper.find(AppErrorBoundary).length).toBe(1)
window.__ERROR__ = oldWindowError
})
})
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work! 🏅 Small gradual improvements to test coverage are the way we want to do better over time

@bfeister bfeister self-requested a review December 21, 2022 23:02
@alexvuong alexvuong merged commit a0c629a into develop Dec 21, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants