Skip to content

Commit

Permalink
fix(react): ensure browser environment before accessing browser-relat…
Browse files Browse the repository at this point in the history
…ed objects
  • Loading branch information
isner committed Mar 28, 2022
1 parent 9730c96 commit 76098bb
Show file tree
Hide file tree
Showing 8 changed files with 39 additions and 6 deletions.
6 changes: 6 additions & 0 deletions packages/react/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,9 @@ and pull in the styles:
```sh
$ npm install @deque/cauldron-styles --save
```

## server-side rendering

Avoid referencing `window` properties, such as `document`, unless you are sure the code will only be executed in a browser environment. For instance, it is safe to reference `document` in an [Effect Hook](https://reactjs.org/docs/hooks-effect.html) or a lifecycle method like `componentDidMount()`, but not in the `render()` method of a class component.

Ensuring you only reference these objects when it is safe to do so will ensure that library consumers can use Cauldron with platforms that use an SSR engine, such as GatsbyJS and NextJS.
6 changes: 4 additions & 2 deletions packages/react/src/components/Dialog/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import Icon from '../Icon';
import ClickOutsideListener from '../ClickOutsideListener';
import AriaIsolate from '../../utils/aria-isolate';
import setRef from '../../utils/setRef';
import { isBrowser } from '../../utils/is-browser';

export interface DialogProps extends React.HTMLAttributes<HTMLDivElement> {
children: React.ReactNode;
Expand Down Expand Up @@ -97,14 +98,15 @@ export default class Dialog extends React.Component<DialogProps, DialogState> {
closeButtonText,
heading,
show,
portal = document.body,
...other
} = this.props;

if (!show) {
if (!show || !isBrowser()) {
return null;
}

const portal = this.props.portal || document.body;

const close = !forceAction ? (
<button className="Dialog__close" type="button" onClick={this.close}>
<Icon type="close" aria-hidden="true" />
Expand Down
6 changes: 4 additions & 2 deletions packages/react/src/components/Pointout/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import focusable from 'focusable';
import Icon from '../Icon';
import rndid from '../../utils/rndid';
import removeIds from '../../utils/remove-ids';
import { isBrowser } from '../../utils/is-browser';

export interface PointoutProps {
arrowPosition:
Expand Down Expand Up @@ -370,16 +371,17 @@ export default class Pointout extends React.Component<
className,
target,
disableOffscreenPointout,
portal = document.body,
previousButtonProps,
nextButtonProps,
closeButtonProps
} = this.props;

if (!show) {
if (!show || !isBrowser()) {
return null;
}

const portal = this.props.portal || document.body;

const FTPO = (
<div
className={classNames(className, 'Pointout', {
Expand Down
4 changes: 4 additions & 0 deletions packages/react/src/components/SkipLink/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import { isBrowser } from '../../utils/is-browser';

export interface SkipLinkProps {
target: string;
Expand Down Expand Up @@ -73,6 +74,9 @@ export default class SkipLink extends React.Component<
}

private onClick() {
if (!isBrowser()) {
return;
}
const element = document.querySelector(this.props.target) as HTMLElement;

if (element) {
Expand Down
3 changes: 2 additions & 1 deletion packages/react/src/components/Tooltip/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import PropTypes from 'prop-types';
import { useId } from 'react-id-generator';
import { Placement } from '@popperjs/core';
import { usePopper } from 'react-popper';
import { isBrowser } from '../../utils/is-browser';

const TIP_HIDE_DELAY = 100;

Expand Down Expand Up @@ -177,7 +178,7 @@ export default function Tooltip({
}
}, [targetElement, id]);

return showTooltip || hideElementOnHidden
return (showTooltip || hideElementOnHidden) && isBrowser()
? createPortal(
<div
id={id}
Expand Down
7 changes: 7 additions & 0 deletions packages/react/src/utils/is-browser/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export function isBrowser(): boolean {
return (
typeof window !== 'undefined' &&
!!window.document &&
!!window.document.createElement
);
}
5 changes: 5 additions & 0 deletions packages/react/src/utils/query-all/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { isBrowser } from '../is-browser';

/**
* A querySelectorAll that returns a normal array rather than live node list.
*
Expand All @@ -6,6 +8,9 @@
* @return {Array}
*/
const queryAll = (selector: string, context = document) => {
if (!isBrowser()) {
return [];
}
return Array.prototype.slice.call(context.querySelectorAll(selector));
};

Expand Down
8 changes: 7 additions & 1 deletion packages/react/src/utils/viewport/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
import { MENU_BREAKPOINT } from '../../constants';
import { isBrowser } from '../is-browser';

export const isWide = () => window.innerWidth >= MENU_BREAKPOINT;
export const isWide = () => {
if (!isBrowser()) {
return false;
}
return window.innerWidth >= MENU_BREAKPOINT;
};

0 comments on commit 76098bb

Please sign in to comment.