Skip to content
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
5 changes: 5 additions & 0 deletions .changeset/every-paws-wish.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"json-schema-studio": patch
---

Fix search results not refreshing when schema is modified while search is active
76 changes: 58 additions & 18 deletions src/components/GraphView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -305,12 +305,26 @@ const GraphView = ({
const trimmed = searchString.trim();

const timeout = setTimeout(() => {
if (!trimmed || trimmed.length < 3) {
setMatchedNodes([]);
if (!trimmed) {
setMatchedNodes((prev) => (prev.length > 0 ? [] : prev));
setCurrentMatchIndex(0);
setErrorMessage("");
setNodes((nds) => nds.map((n) => ({ ...n, selected: false })));
fitView({ duration: 800, padding: 0.05 });

setNodes((nds) => {
let hasSelected = false;
const newNodes = nds.map((n) => {
if (n.selected) hasSelected = true;
return { ...n, selected: false };
});

if (hasSelected) {
setTimeout(() => {
fitView({ duration: 800, padding: 0.05 });
}, 0);
return newNodes;
}
return nds;
});
return;
}

Expand All @@ -324,26 +338,34 @@ const GraphView = ({
return searchWords.every((word) => titleKeyWords.includes(word));
});

setMatchedNodes(foundNodes);
setMatchedNodes((prev) => {
if (
prev.length === foundNodes.length &&
prev.every((n, i) => n.id === foundNodes[i].id)
) {
return prev;
}
return foundNodes;
});

if (foundNodes.length > 0) {
setCurrentMatchIndex(0);
const firstNode = foundNodes[0];
const x = firstNode.position.x + NODE_WIDTH / 2;
const y = firstNode.position.y + NODE_HEIGHT / 2;

setSelectedNode({
id: firstNode.id,
});

setCenter(x, y, { zoom: Math.max(getZoom(), 1), duration: 500 });
const targetNode = foundNodes[currentMatchIndex % foundNodes.length];

setNodes((nds) => {
let changed = false;
const newNodes = nds.map((n) => {
const selected = n.id === firstNode.id;
const selected = n.id === targetNode.id;
if (n.selected !== selected) changed = true;
return { ...n, selected };
});

if (changed) {
const x = targetNode.position.x + NODE_WIDTH / 2;
const y = targetNode.position.y + NODE_HEIGHT / 2;
setTimeout(() => {
setCenter(x, y, { zoom: Math.max(getZoom(), 1), duration: 500 });
}, 0);
}
return changed ? newNodes : nds;
});

Expand All @@ -356,12 +378,28 @@ const GraphView = ({
}, 300);

return () => clearTimeout(timeout);
}, [searchString]);
}, [searchString, nodes, currentMatchIndex]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check ESLint configuration for react-hooks/exhaustive-deps rule
fd -t f -e js -e json -e yaml -e yml '.eslintrc' --exec cat {} 2>/dev/null | head -50
cat package.json | jq '.eslintConfig // empty'

Repository: ioflux-org/studio-json-schema

Length of output: 55


🏁 Script executed:

# First, get the size of GraphView.tsx
wc -l src/components/GraphView.tsx

Repository: ioflux-org/studio-json-schema

Length of output: 105


🏁 Script executed:

# Read the relevant section around line 365
sed -n '350,380p' src/components/GraphView.tsx

Repository: ioflux-org/studio-json-schema

Length of output: 936


🏁 Script executed:

# Search for useReactFlow usage in the file
rg "useReactFlow" src/components/GraphView.tsx -B 2 -A 2

Repository: ioflux-org/studio-json-schema

Length of output: 401


🏁 Script executed:

# Check for ESLint configuration in the repo more thoroughly
find . -type f \( -name ".eslintrc*" -o -name "eslint.config.*" \) 2>/dev/null
cat package.json | jq '.eslintConfig' 2>/dev/null || echo "No eslintConfig in package.json"

Repository: ioflux-org/studio-json-schema

Length of output: 96


🏁 Script executed:

# Read a larger section to see the full effect body and where it starts
sed -n '320,375p' src/components/GraphView.tsx

Repository: ioflux-org/studio-json-schema

Length of output: 1814


🏁 Script executed:

# Check the ESLint configuration
cat eslint.config.js

Repository: ioflux-org/studio-json-schema

Length of output: 888


🏁 Script executed:

# Check if fitView is used anywhere in the effect starting from line 320 to 375
sed -n '310,380p' src/components/GraphView.tsx | grep -n "fitView"

Repository: ioflux-org/studio-json-schema

Length of output: 55


🏁 Script executed:

# Get more context - find where this effect starts to understand its full scope
sed -n '290,375p' src/components/GraphView.tsx

Repository: ioflux-org/studio-json-schema

Length of output: 2641


Add missing dependencies to the effect's dependency array.

The effect uses fitView, setCenter, and getZoom but doesn't include them in the dependency array. While these functions from useReactFlow are typically stable, ESLint's react-hooks/exhaustive-deps rule (which is enabled in this project) flags them as missing dependencies.

Additionally, including currentMatchIndex means this effect re-runs when the user navigates via navigateMatch. The guards prevent double-centering, but the effect still performs filtering and comparison work on each navigation. If performance becomes a concern, consider separating the "search on input/nodes change" logic from "center on navigation" logic.

Suggested fix
- }, [searchString, nodes, currentMatchIndex]);
+ }, [searchString, nodes, currentMatchIndex, fitView, setCenter, getZoom]);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
}, [searchString, nodes, currentMatchIndex]);
}, [searchString, nodes, currentMatchIndex, fitView, setCenter, getZoom]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/GraphView.tsx` at line 365, The effect that currently lists
[searchString, nodes, currentMatchIndex] should include the stable functions
from useReactFlow—specifically fitView, setCenter, and getZoom—in its dependency
array to satisfy react-hooks/exhaustive-deps; update the dependency array used
in the effect inside GraphView.tsx (the effect that references fitView,
setCenter, and getZoom) to include these three symbols, and if desired later for
performance split the effect into two (one for filtering on searchString/nodes
and another for centering on currentMatchIndex) to avoid re-running filtering
when navigating matches.


const handleKeyDown = useCallback(
(e: React.KeyboardEvent<HTMLDivElement>) => {
if (matchCount <= 1) return;

if (e.key === "ArrowRight" || e.key === "Enter") {
e.preventDefault();
navigateMatch("next");
} else if (e.key === "ArrowLeft") {
e.preventDefault();
navigateMatch("prev");
}
},
[matchCount, navigateMatch]
);

return (
<div
ref={containerRef}
tabIndex={0}
// onKeyDown={handleKeyDown}
onKeyDown={handleKeyDown}
className="relative w-full h-full"
>
<ReactFlow
Expand All @@ -374,6 +412,8 @@ const GraphView = ({
nodeTypes={nodeTypes}
minZoom={0.05}
maxZoom={5}
fitView
fitViewOptions={{ padding: 0.05, duration: 800 }}
onEdgeMouseEnter={(_, edge) => setHoveredEdgeId(edge.id)}
onEdgeMouseLeave={() => setHoveredEdgeId(null)}
onPaneClick={() => {
Expand Down
Loading