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

[ARGG-876] Add support for withScrimmedPortal with SSR #3085

Merged
merged 5 commits into from
Nov 22, 2023
Merged

Conversation

anambl
Copy link
Contributor

@anambl anambl commented Nov 15, 2023

The WithScrimmedPortal BPK component is not compatible with SSR due to the use of the React Portal. To be able to use the component in projects that use SSR, we should provide a fallback behaviour that can be rendered on the server. The fallback behaviour should be the BpkScrim component, however, in the future we can allow consumers to pass another component if needed.

Remember to include the following changes:

  • README.md (If you have created a new component)
  • Component README.md
  • Tests
  • Storybook examples created/updated
  • For breaking changes or deprecating components/properties, migration guides added to the description of the PR. If the guide has large changes, consider creating a new Markdown page inside the component's docs folder and link it here

@anambl anambl added the minor Non breaking change label Nov 15, 2023
Copy link

Visit https://backpack.github.io/storybook-prs/3085 to see this build running in a browser.

@anambl anambl marked this pull request as ready for review November 16, 2023 09:18
* If consumer passes runOnServer as true (meaning they want to render the component on server and client), we render the component as normal once the component has been mounted.
* This is to ensure the snapshotted markup (initial render before the component has been mounted) is the same on both server and client.
*/
if (!runOnServer || isPortalReady) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Would we expect a consumer to be aware of where the component is being rendered (server or browser) and act accordingly to pass a prop in? If so, this is a code smell to me.

Modern web frameworks like Remix have no real concept of "running on server" or "running on browser". The expectation is all components are executed in both environments and it's common practice for components to be required to be self aware of the where they are being rendered, and thus act accordingly.

What's the reason we don't want this to be self aware?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Modern web frameworks like Remix have no real concept of "running on server" or "running on browser". The expectation is all components are executed in both environments

I didn't know that, but I think that's still ok as then you just pass the runOnServer prop which means it runs on both (not just server)? we can make that the default instead (this component is not used anywhere, so we have the freedom of making any change to it).

and it's common practice for components to be required to be self aware of the where they are being rendered, and thus act accordingly.

How can we tell if a component is running on server?
I guess my thoughts here were that consumers can choose whether they want to render on both and use the BpkScrim on server, OR just render it on client.

Copy link
Contributor

Choose a reason for hiding this comment

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

I didn't know that, but I think that's still ok as then you just pass the runOnServer prop which means it runs on both (not just server)?

Then the naming here should be improved, runOnServer suggests only the server.

But it still raises the question, do we need flexibility here? Why can't this component decide what should happen depending on the environment?

How can we tell if a component is running on server?

common practice is to check for the presence of window, but it depends what the requirements are. If we need the document to be available, we should check for that.

if (typeof window === 'undefined')

Copy link
Contributor

Choose a reason for hiding this comment

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

totally agree with Chris and probably that's the source of my confusion in the other comment

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Then the naming here should be improved, runOnServer suggests only the server.

I thought we always run on either just client, or both client and server so it's clear that this will run on both (although also added a comment above to explain it)

But it still raises the question, do we need flexibility here? Why can't this component decide what should happen depending on the environment?

The component does decide what should happen depending on the env, I just didn't want to force the BpkScrim on the initial render on the client unless required i.e. to match the server initial render also thinking whether maybe in the future we want to render something else on the server, not the BpkScrim

Maybe this comment helps here https://github.com/Skyscanner/backpack/pull/3085/files#r1395443998

Copy link
Contributor

Choose a reason for hiding this comment

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

Do all our microsites run on both client and server completely?

There's a big mix. Banana is CSR only. Falcon and hotels do SSR. OCs SSR the HTML and deliver it when requested.

Copy link
Contributor

Choose a reason for hiding this comment

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

Agree with previous comments here, replacing runOnServer prop and it's associated behaviour with if (typeof window === 'undefined') should do the trick 👍

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah, yes, that'll work too, we don't need the prop

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've removed the prop, although just to clarify - the point of the runOnServer prop was to set the expectation whether the component will run only on client only or if it'll run on both. I think using the window object helps us determine whether at run time it's running on server or on client, but I think achieving the same as the prop (i.e. if the intent is for the component to run on both client and server, or just on client so that for client-only projects we don't need to first render the scrim) makes it confusing as when it's running on client we'd need to check if it's already ran on server 🤔

Copy link
Contributor

Choose a reason for hiding this comment

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

we'd need to check if it's already ran on server

Can you explain what you mean by this? I don't quite follow

Copy link

Visit https://backpack.github.io/storybook-prs/3085 to see this build running in a browser.

Comment on lines 54 to 56
useEffect(() => {
setIsPortalReady(true);
}, []);
Copy link
Contributor

Choose a reason for hiding this comment

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

Given all the discussion regarding double render and the general strategy being taken here, I recommend linking to the react docs: https://react.dev/reference/react-dom/hydrate#handling-different-client-and-server-content and popping some of the context from the comments directly into code.

Copy link

Visit https://backpack.github.io/storybook-prs/3085 to see this build running in a browser.

Copy link

Visit https://backpack.github.io/storybook-prs/3085 to see this build running in a browser.

});

it('renders the wrapped component inside a portal with renderTarget provided', () => {
const WrappedComponent = () => <div>Wrapped Component</div>;
const ScrimmedComponent = withScrimmedPortal(WrappedComponent);
const DialogContent = () => <div data-testid="dialog-content">Dialog content</div>;
Copy link
Contributor

Choose a reason for hiding this comment

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

what's the difference between this test and the next one?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

hmm, yeah, you're right. Nothing really, I'll remove it.

});

it('renders the wrapped component inside a portal with renderTarget provided', () => {
const WrappedComponent = () => <div>Wrapped Component</div>;
const ScrimmedComponent = withScrimmedPortal(WrappedComponent);
const DialogContent = () => <div data-testid="dialog-content">Dialog content</div>;
Copy link
Contributor

Choose a reason for hiding this comment

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

what's the difference between this test and the next one?

@@ -17,7 +17,7 @@
*/

import '@testing-library/jest-dom';
import { render, within, screen } from '@testing-library/react';
import { render, within, screen, isInaccessible } from '@testing-library/react';
Copy link
Contributor

Choose a reason for hiding this comment

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

is isInaccessible used somewhere?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ohh no, I'd added it, but then removed it
I'll remove this, I thought the linting would do that 😅

Copy link
Contributor

@runmoore runmoore left a comment

Choose a reason for hiding this comment

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

@anambl and I had a chat on zoom. I'm happy with the implementation that is currently in the PR, so approving on he basis that @arnaugm also gives his approval too :)

Copy link

Visit https://backpack.github.io/storybook-prs/3085 to see this build running in a browser.

@anambl anambl merged commit 2fc97b4 into main Nov 22, 2023
9 checks passed
@anambl anambl deleted the ARGG-876 branch November 22, 2023 14:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
minor Non breaking change
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants