Advanced patterns and techniques for React Dev Panel.
Call useDevPanel from different components:
// UserProfile.tsx
function UserProfile() {
useDevPanel("User Profile", {
name: { type: "text", value: name, onChange: setName },
avatar: { type: "text", value: avatar, onChange: setAvatar },
});
}
// AppSettings.tsx
function AppSettings() {
useDevPanel("App Settings", {
theme: { type: "select", value: theme, options: ["light", "dark"], onChange: setTheme },
});
}Both sections appear in the same panel automatically.
interface Settings {
ui: { theme: string; fontSize: number };
api: { endpoint: string; timeout: number };
}
const [settings, setSettings] = useState<Settings>({...});
useDevPanel("Settings", {
theme: {
type: "select",
value: settings.ui.theme,
options: ["light", "dark"],
onChange: (value) => setSettings(prev => ({
...prev,
ui: { ...prev.ui, theme: value }
})),
},
});// Redux
import { useDispatch, useSelector } from "react-redux";
function Component() {
const theme = useSelector((state) => state.theme);
const dispatch = useDispatch();
useDevPanel("Redux", {
theme: {
type: "select",
value: theme,
options: ["light", "dark"],
onChange: (value) => dispatch(setTheme(value)),
},
});
}
// Zustand
import { useStore } from "./store";
function Component() {
const { theme, setTheme } = useStore();
useDevPanel("Store", {
theme: { type: "select", value: theme, options: ["light", "dark"], onChange: setTheme },
});
}Generate controls dynamically:
const colorKeys = ["primary", "secondary", "accent"];
const colorControls = colorKeys.reduce(
(acc, key) => ({
...acc,
[key]: {
type: "color",
value: colors[key],
label: key,
onChange: (value) => setColor(key, value),
},
}),
{},
);
useDevPanel("Colors", colorControls);Show controls based on conditions:
const controls = {
mode: { type: "select", value: mode, options: ["basic", "advanced"], onChange: setMode },
...(mode === "advanced" && {
advancedOption: { type: "boolean", value: advanced, onChange: setAdvanced },
}),
};
useDevPanel("Settings", controls);const controls = useMemo(
() => ({
name: { type: "text", value: name, onChange: setName },
age: { type: "number", value: age, onChange: setAge },
}),
[name, age],
);
useDevPanel("User", controls);import { useDebounceCallback } from "@berenjena/react-dev-panel";
const debouncedSave = useDebounceCallback(async (value) => {
await saveToApi(value);
}, 500);
useDevPanel("API", {
data: {
type: "text",
value: data,
onChange: (value) => {
setData(value);
debouncedSave(value);
},
},
});import { useHotkeys, createHotkey } from "@berenjena/react-dev-panel";
useHotkeys(
[
createHotkey("s", handleSave, { ctrl: true }, { description: "Save" }),
createHotkey("r", handleReset, { ctrl: true, shift: true }),
createHotkey("/", toggleSearch),
],
{ enabled: true },
);import { isMacOS } from "@berenjena/react-dev-panel";
const modifier = isMacOS() ? "Cmd" : "Ctrl";
useDevPanel("Settings", controls, {
hotKeyConfig: {
key: "d",
[isMacOS() ? "metaKey" : "ctrlKey"]: true,
},
});Group related actions:
useDevPanel("Actions", {
fileActions: {
type: "buttonGroup",
buttons: [
{ label: "Save", onClick: handleSave },
{ label: "Load", onClick: handleLoad },
{ label: "Reset", onClick: handleReset },
],
},
});Organize controls visually:
useDevPanel("Settings", {
theme: { type: "select", ...},
separator1: { type: "separator", variant: "line" },
apiKey: { type: "text", ...},
separator2: { type: "separator", variant: "label", label: "Advanced" },
debug: { type: "boolean", ...},
});Handle file uploads:
const [files, setFiles] = useState<FileList | null>(null);
useDevPanel("Upload", {
files: {
type: "dragAndDrop",
onChange: (fileList) => {
setFiles(fileList);
processFiles(fileList);
},
},
});import type { ControlsGroup } from "@berenjena/react-dev-panel";
const controls: ControlsGroup = {
name: { type: "text", value: name, onChange: setName },
// TypeScript enforces correct types
};function createControl<T>(type: string, value: T, onChange: (v: T) => void) {
return { type, value, onChange };
}
const controls = {
name: createControl("text", name, setName),
age: createControl("number", age, setAge),
};The panel auto-unmounts when all sections are removed. Manual cleanup:
useEffect(() => {
useDevPanel("Temporary", controls);
return () => {
// Auto-cleanup happens here
};
}, []);- Group related controls in the same section
- Use descriptive section names (stable, not dynamic)
- Memoize controls for performance
- Enable persistence for user preferences
- Use onBlur for expensive operations
- Debounce heavy computations
- TypeScript for type safety
const [flags, setFlags] = useState({
newUI: false,
betaFeatures: false,
});
useDevPanel("Feature Flags", {
newUI: {
type: "boolean",
value: flags.newUI,
onChange: (v) => setFlags((prev) => ({ ...prev, newUI: v })),
},
betaFeatures: {
type: "boolean",
value: flags.betaFeatures,
onChange: (v) => setFlags((prev) => ({ ...prev, betaFeatures: v })),
},
});const [mock, setMock] = useState({ enabled: false, delay: 0 });
useDevPanel("API Mock", {
enabled: { type: "boolean", value: mock.enabled, onChange: (v) => setMock((prev) => ({ ...prev, enabled: v })) },
delay: { type: "range", value: mock.delay, min: 0, max: 5000, step: 100, onChange: (v) => setMock((prev) => ({ ...prev, delay: v })) },
});const themes = ["light", "dark", "auto"];
const [theme, setTheme] = useState("dark");
useDevPanel("Theme", {
theme: {
type: "select",
value: theme,
options: themes,
persist: true,
onChange: (value) => {
setTheme(value);
document.documentElement.setAttribute("data-theme", value);
},
},
});