Skip to content

Commit

Permalink
feat(ui): lazy load modals
Browse files Browse the repository at this point in the history
This uses React code splitting to only load modal content components if they are used - all modal components will be loaded the first time user open a modal, rather than on initial page load
  • Loading branch information
prymitive committed May 11, 2019
1 parent 861ed1c commit eb1181c
Show file tree
Hide file tree
Showing 4 changed files with 66 additions and 16 deletions.
29 changes: 22 additions & 7 deletions ui/src/Components/MainModal/index.js
Expand Up @@ -6,12 +6,19 @@ import { observable, action } from "mobx";

import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faCog } from "@fortawesome/free-solid-svg-icons/faCog";
import { faSpinner } from "@fortawesome/free-solid-svg-icons/faSpinner";

import { AlertStore } from "Stores/AlertStore";
import { Settings } from "Stores/Settings";
import { TooltipWrapper } from "Components/TooltipWrapper";
import { Modal } from "Components/Modal";
import { MainModalContent } from "./MainModalContent";

// https://github.com/facebook/react/issues/14603
const MainModalContent = React.lazy(() =>
import("./MainModalContent").then(module => ({
default: module.MainModalContent
}))
);

const MainModal = observer(
class MainModal extends Component {
Expand Down Expand Up @@ -49,12 +56,20 @@ const MainModal = observer(
</TooltipWrapper>
</li>
<Modal isOpen={this.toggle.show}>
<MainModalContent
alertStore={alertStore}
settingsStore={settingsStore}
onHide={this.toggle.hide}
isVisible={this.toggle.show}
/>
<React.Suspense
fallback={
<h1 className="display-1 text-secondary p-5 m-auto">
<FontAwesomeIcon icon={faSpinner} size="lg" spin />
</h1>
}
>
<MainModalContent
alertStore={alertStore}
settingsStore={settingsStore}
onHide={this.toggle.hide}
isVisible={this.toggle.show}
/>
</React.Suspense>
</Modal>
</React.Fragment>
);
Expand Down
12 changes: 11 additions & 1 deletion ui/src/Components/MainModal/index.test.js
Expand Up @@ -31,12 +31,22 @@ describe("<MainModal />", () => {
expect(tree.find("MainModalContent")).toHaveLength(0);
});

it("renders the modal when it is shown", () => {
it("renders a spinner placeholder while modal content is loading", () => {
const tree = MountedMainModal();
const toggle = tree.find(".nav-link");
toggle.simulate("click");
expect(tree.find("FontAwesomeIcon")).not.toHaveLength(0);
expect(tree.find("MainModalContent")).toHaveLength(0);
expect(tree.find(".modal-content").find("svg.fa-spinner")).toHaveLength(1);
});

it("renders modal content if fallback is not used", () => {
const tree = MountedMainModal();
const toggle = tree.find(".nav-link");
toggle.simulate("click");
expect(tree.find("FontAwesomeIcon")).not.toHaveLength(0);
expect(tree.find("MainModalContent")).toHaveLength(1);
expect(tree.find(".modal-content").find("svg.fa-spinner")).toHaveLength(0);
});

it("hides the modal when toggle() is called twice", () => {
Expand Down
29 changes: 22 additions & 7 deletions ui/src/Components/SilenceModal/index.js
Expand Up @@ -5,16 +5,23 @@ import { observer } from "mobx-react";

import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faBellSlash } from "@fortawesome/free-solid-svg-icons/faBellSlash";
import { faSpinner } from "@fortawesome/free-solid-svg-icons/faSpinner";

import { AlertStore } from "Stores/AlertStore";
import { SilenceFormStore } from "Stores/SilenceFormStore";
import { Settings } from "Stores/Settings";
import { Modal } from "Components/Modal";
import { TooltipWrapper } from "Components/TooltipWrapper";
import { SilenceModalContent } from "./SilenceModalContent";

import "./index.css";

// https://github.com/facebook/react/issues/14603
const SilenceModalContent = React.lazy(() =>
import("./SilenceModalContent").then(module => ({
default: module.SilenceModalContent
}))
);

const SilenceModal = observer(
class SilenceModal extends Component {
static propTypes = {
Expand Down Expand Up @@ -42,12 +49,20 @@ const SilenceModal = observer(
isOpen={silenceFormStore.toggle.visible}
onExited={silenceFormStore.data.resetProgress}
>
<SilenceModalContent
alertStore={alertStore}
silenceFormStore={silenceFormStore}
settingsStore={settingsStore}
onHide={silenceFormStore.toggle.hide}
/>
<React.Suspense
fallback={
<h1 className="display-1 text-secondary p-5 m-auto">
<FontAwesomeIcon icon={faSpinner} size="lg" spin />
</h1>
}
>
<SilenceModalContent
alertStore={alertStore}
silenceFormStore={silenceFormStore}
settingsStore={settingsStore}
onHide={silenceFormStore.toggle.hide}
/>
</React.Suspense>
</Modal>
</React.Fragment>
);
Expand Down
12 changes: 11 additions & 1 deletion ui/src/Components/SilenceModal/index.test.js
Expand Up @@ -39,12 +39,22 @@ describe("<SilenceModal />", () => {
expect(tree.find("SilenceModalContent")).toHaveLength(0);
});

it("renders the modal when it is shown", () => {
it("renders a spinner placeholder while modal content is loading", () => {
const tree = MountedSilenceModal();
const toggle = tree.find(".nav-link");
toggle.simulate("click");
expect(tree.find("FontAwesomeIcon")).not.toHaveLength(0);
expect(tree.find("SilenceModalContent")).toHaveLength(0);
expect(tree.find(".modal-content").find("svg.fa-spinner")).toHaveLength(1);
});

it("renders modal content if fallback is not used", () => {
const tree = MountedSilenceModal();
const toggle = tree.find(".nav-link");
toggle.simulate("click");
expect(tree.find("FontAwesomeIcon")).not.toHaveLength(0);
expect(tree.find("SilenceModalContent")).toHaveLength(1);
expect(tree.find(".modal-content").find("svg.fa-spinner")).toHaveLength(0);
});

it("hides the modal when toggle() is called twice", () => {
Expand Down

0 comments on commit eb1181c

Please sign in to comment.