Skip to content

Surya4419/i330/unnecessary re render on sample selection #338

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

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions src/AgreementHtml.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ import { Spin } from "antd";
import useAppStore from "./store/store";
import FullScreenModal from "./components/FullScreenModal";

function AgreementHtml({ loading, isModal }: { loading: boolean; isModal?: boolean }) {
function AgreementHtml({ isModal }: { isModal?: boolean; loading?: boolean }) {
const agreementHtml = useAppStore((state) => state.agreementHtml);
const backgroundColor = useAppStore((state) => state.backgroundColor);
const textColor = useAppStore((state) => state.textColor);
const isLoading = useAppStore((state) => state.isLoading);

return (
<div
Expand Down Expand Up @@ -38,9 +39,9 @@ function AgreementHtml({ loading, isModal }: { loading: boolean; isModal?: boole
<p style={{ textAlign: "center", color: textColor }}>
The result of merging the JSON data with the template.
</p>
{loading ? (
<div style={{ flex: 1, display: "flex", justifyContent: "center", alignItems: "center" }}>
<Spin indicator={<LoadingOutlined style={{ fontSize: 42, color: "#19c6c7" }} spin />} />
{isLoading ? (
<div style={{ flex: 1, display: 'flex', justifyContent: 'center', alignItems: 'center', position: 'relative' }}>
<Spin indicator={<LoadingOutlined style={{ fontSize: 42, color: '#19c6c7' }} spin />} />
</div>
) : (
<div
Expand Down
15 changes: 8 additions & 7 deletions src/components/SampleDropdown.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,27 @@
import React from "react";
import { Button, Dropdown, Space, message, MenuProps } from "antd";
import { DownOutlined } from "@ant-design/icons";
import { useCallback, useMemo, useState } from "react";
import { useCallback, useMemo } from "react";
import useAppStore from "../store/store";
import { shallow } from "zustand/shallow";
import { useStoreWithEqualityFn } from "zustand/traditional";

function SampleDropdown({
const SampleDropdown = function SampleDropdown({
setLoading,
}: {
setLoading: React.Dispatch<React.SetStateAction<boolean>>;
}): JSX.Element {
const { samples, loadSample } = useStoreWithEqualityFn(
const { samples, loadSample, sampleName } = useStoreWithEqualityFn(
useAppStore,
(state) => ({
samples: state.samples,
loadSample: state.loadSample as (key: string) => Promise<void>,
sampleName: state.sampleName
}),
shallow
);

const [selectedSample, setSelectedSample] = useState<string | null>(null);


const items: MenuProps["items"] = useMemo(
() =>
Expand All @@ -37,7 +39,7 @@ function SampleDropdown({
try {
await loadSample(e.key);
void message.info(`Loaded ${e.key} sample`);
setSelectedSample(e.key);

} catch (error) {
void message.error("Failed to load sample");
} finally {
Expand All @@ -54,12 +56,11 @@ function SampleDropdown({
<Dropdown menu={{ items, onClick: (e) => void handleMenuClick(e) }} trigger={["click"]}>
<div className="samples-element">
<Button aria-label="Load sample dropdown">
{selectedSample ? selectedSample : "Load Sample"} <DownOutlined />
{sampleName || "Load Sample"} <DownOutlined />
</Button>
</div>
</Dropdown>
</Space>
);
}

export default SampleDropdown;
78 changes: 52 additions & 26 deletions src/store/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ interface AppState {
sampleName: string;
backgroundColor: string;
textColor: string;
isLoading: boolean;
setTemplateMarkdown: (template: string) => Promise<void>;
setEditorValue: (value: string) => void;
setModelCto: (model: string) => Promise<void>;
Expand All @@ -44,8 +45,6 @@ export interface DecompressedData {
agreementHtml: string;
}

const rebuildDeBounce = debounce(rebuild, 500);

async function rebuild(template: string, model: string, dataString: string) {
const modelManager = new ModelManager({ strict: true });
modelManager.addCTOModel(model, undefined, true);
Expand All @@ -69,6 +68,11 @@ async function rebuild(template: string, model: string, dataString: string) {
);
}

const rebuildDeBounce = debounce(async (template: string, model: string, data: string) => {
const result = await rebuild(template, model, data);
return result;
}, 500);

const useAppStore = create<AppState>()(
immer(
devtools((set, get) => ({
Expand All @@ -83,14 +87,15 @@ const useAppStore = create<AppState>()(
editorAgreementData: JSON.stringify(playground.DATA, null, 2),
agreementHtml: "",
error: undefined,
isLoading: false,
samples: SAMPLES,
init: async () => {
const params = new URLSearchParams(window.location.search);
const compressedData = params.get("data");
if (compressedData) {
await get().loadFromLink(compressedData);
} else {
await get().rebuild();
await get().loadSample(playground.NAME);
}
},
loadSample: async (name: string) => {
Expand All @@ -107,24 +112,33 @@ const useAppStore = create<AppState>()(
data: JSON.stringify(sample.DATA, null, 2),
editorAgreementData: JSON.stringify(sample.DATA, null, 2),
}));
await get().rebuild();
get().rebuild();
}
},
rebuild: async () => {
const { templateMarkdown, modelCto, data } = get();
set(() => ({ isLoading: true }));
try {
const result = await rebuildDeBounce(templateMarkdown, modelCto, data);
set(() => ({ agreementHtml: result, error: undefined })); // Clear error on success
const result = await rebuildDeBounce(
get().templateMarkdown,
get().modelCto,
get().data
);
set(() => ({ agreementHtml: result, error: undefined }));
} catch (error: any) {
set(() => ({ error: formatError(error) }));
} finally {
set(() => ({ isLoading: false }));
}
},
setTemplateMarkdown: async (template: string) => {
set(() => ({ templateMarkdown: template }));
set(() => ({
templateMarkdown: template,
editorValue: template
}));
const { modelCto, data } = get();
try {
const result = await rebuildDeBounce(template, modelCto, data);
set(() => ({ agreementHtml: result, error: undefined })); // Clear error on success
set(() => ({ agreementHtml: result, error: undefined }));
} catch (error: any) {
set(() => ({ error: formatError(error) }));
}
Expand All @@ -133,11 +147,14 @@ const useAppStore = create<AppState>()(
set(() => ({ editorValue: value }));
},
setModelCto: async (model: string) => {
set(() => ({ modelCto: model }));
set(() => ({
modelCto: model,
editorModelCto: model
}));
const { templateMarkdown, data } = get();
try {
const result = await rebuildDeBounce(templateMarkdown, model, data);
set(() => ({ agreementHtml: result, error: undefined })); // Clear error on success
set(() => ({ agreementHtml: result, error: undefined }));
} catch (error: any) {
set(() => ({ error: formatError(error) }));
}
Expand All @@ -146,14 +163,17 @@ const useAppStore = create<AppState>()(
set(() => ({ editorModelCto: value }));
},
setData: async (data: string) => {
set(() => ({ data }));
set(() => ({
data,
editorAgreementData: data
}));
try {
const result = await rebuildDeBounce(
get().templateMarkdown,
get().modelCto,
data
);
set(() => ({ agreementHtml: result, error: undefined })); // Clear error on success
set(() => ({ agreementHtml: result, error: undefined }));
} catch (error: any) {
set(() => ({ error: formatError(error) }));
}
Expand All @@ -162,21 +182,27 @@ const useAppStore = create<AppState>()(
set(() => ({ editorAgreementData: value }));
},
generateShareableLink: () => {
const state = get();
const compressedData = compress({
templateMarkdown: state.templateMarkdown,
modelCto: state.modelCto,
data: state.data,
agreementHtml: state.agreementHtml,
});
return `${window.location.origin}?data=${compressedData}`;
try {
const state = get();
const compressedData = compress({
templateMarkdown: state.templateMarkdown,
modelCto: state.modelCto,
data: state.data,
agreementHtml: state.agreementHtml,
});
return `${window.location.origin}?data=${compressedData}`;
} catch (error) {
set(() => ({ error: 'Failed to generate share link: ' + (error instanceof Error ? error.message : 'Unknown error') }));
return window.location.href;
}
},
loadFromLink: async (compressedData: string) => {
try {
const { templateMarkdown, modelCto, data, agreementHtml } = decompress(compressedData);
if (!templateMarkdown || !modelCto || !data) {
throw new Error("Invalid share link data");
const decompressed = decompress(compressedData);
if (!decompressed?.templateMarkdown || !decompressed?.modelCto || !decompressed?.data) {
throw new Error("Invalid share link - missing required fields");
}
const { templateMarkdown, modelCto, data, agreementHtml } = decompressed;
set(() => ({
templateMarkdown,
editorValue: templateMarkdown,
Expand All @@ -185,9 +211,9 @@ const useAppStore = create<AppState>()(
data,
editorAgreementData: data,
agreementHtml,
error: undefined,
error: undefined
}));
await get().rebuild();

} catch (error) {
set(() => ({
error: "Failed to load shared content: " + (error instanceof Error ? error.message : "Unknown error"),
Expand Down