fix(widgets): prevent TabsContainer state reset on data refetch
react-jsx-parser auto-generates random keys on every render, causing full remounts that reset uncontrolled Tabs to defaultValue. Fix by: - Adding disableKeyGeneration to preserve React reconciliation - Memoizing bindings to avoid unnecessary re-parses - Converting TabsContainer to controlled Tabs with useState - Validating defaultTab against actual panel values - Clearing stale parseErrors when template/data changes
This commit is contained in:
2 files changed
+12
-3
No files matched your search
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { useCallback, useState } from "react";
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import JsxParser from "react-jsx-parser";
|
||||
import { Alert, Stack, Text } from "@mantine/core";
|
||||
import { IconAlertTriangle } from "@tabler/icons-react";
|
||||
@@ -19,6 +19,11 @@ export default function CustomJsxDisplay({ data }: { data: Record<string, unknow
|
||||
const template = String(data.template ?? "");
|
||||
const apiData = data.data;
|
||||
const [parseErrors, setParseErrors] = useState<string[]>([]);
|
||||
const bindings = useMemo(() => SAFE_BINDINGS(apiData), [apiData]);
|
||||
|
||||
useEffect(() => {
|
||||
setParseErrors([]);
|
||||
}, [template, bindings]);
|
||||
|
||||
const handleError = useCallback((error: Error) => {
|
||||
setParseErrors((prev) => appendParseError(prev, error.message));
|
||||
@@ -39,7 +44,8 @@ export default function CustomJsxDisplay({ data }: { data: Record<string, unknow
|
||||
<JsxParser
|
||||
jsx={template}
|
||||
components={WHITELISTED_COMPONENTS as never}
|
||||
bindings={SAFE_BINDINGS(apiData)}
|
||||
bindings={bindings}
|
||||
disableKeyGeneration
|
||||
componentsOnly
|
||||
allowUnknownElements={false}
|
||||
blacklistedAttrs={[/^on.+/i, /^dangerously/i]}
|
||||
|
||||
@@ -69,9 +69,12 @@ function isTabPanel(child: ReactNode): child is React.ReactElement<TabPanelProps
|
||||
export function TabsContainer({ children, defaultTab }: TabsContainerProps) {
|
||||
const panels = Children.toArray(children).filter(isTabPanel);
|
||||
const firstPanel = panels[0];
|
||||
const panelValues = new Set(panels.map((p) => p.props.value));
|
||||
const initialTab = (defaultTab && panelValues.has(defaultTab) ? defaultTab : firstPanel?.props.value) ?? null;
|
||||
const [activeTab, setActiveTab] = useState<string | null>(initialTab);
|
||||
|
||||
return (
|
||||
<Tabs defaultValue={defaultTab ?? firstPanel?.props.value}>
|
||||
<Tabs value={activeTab} onChange={setActiveTab}>
|
||||
<Tabs.List>
|
||||
{panels.map((panel) => (
|
||||
<Tabs.Tab key={panel.props.value} value={panel.props.value}>
|
||||
|
||||
Reference in new issue
Block a user