Skip to content

Refactor components for improved state management #24

Merged
eloiberlinger1 merged 15 commits into
prodfrom
main
May 11, 2026
Merged

Refactor components for improved state management #24
eloiberlinger1 merged 15 commits into
prodfrom
main

Conversation

@eloiberlinger1
Copy link
Copy Markdown
Member

No description provided.

eloiberlinger1 and others added 15 commits April 17, 2026 16:43
…nd optimize state management for drag and wall loading logic
…agement and improve performance with useMemo and useCallback
refactor(EditorApp, AddHoldModal): improve hold instance handling and…
…ving distinctId and improving type safety in various components
Copilot AI review requested due to automatic review settings May 11, 2026 15:58
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request performs a significant refactoring of the frontend editor, introducing comprehensive TypeScript interfaces to replace 'any' types and optimizing component performance through hooks like useMemo and useCallback. It also updates several dependencies, removes react-helmet-async, and streamlines PostHog analytics integration. Key feedback includes a critical warning that removing '/gym' from the Vite proxy targets will break development API requests. Additionally, the reviewer noted an invalid configuration property in the PostHog initialization, a potential O(N^2) performance bottleneck in the recursive hold tree rendering, and the lack of error handling for optimistic updates during hold deletions.

Comment thread frontend/vite.config.ts
Comment on lines +24 to 25
const targets = ['/api', '/admin', '/static', '/media'];
return Object.fromEntries(targets.map((p) => [p, backendUrl]));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

The removal of /gym from the proxy targets will break API requests in the development environment. Several core components (e.g., AddHoldModal, SidebarHoldsSection, FileManager) rely on endpoints starting with /gym/ to fetch stock data, update layouts, and manage session collections.

Suggested change
const targets = ['/api', '/admin', '/static', '/media'];
return Object.fromEntries(targets.map((p) => [p, backendUrl]));
const targets = ['/api', '/gym', '/admin', '/static', '/media'];

posthog.init(apiKey, {
api_host: host,
ui_host: 'https://eu.posthog.com',
defaults: '2026-01-30',
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

defaults is not a valid configuration option for posthog.init in the PostHog JS SDK. Additionally, the value '2026-01-30' appears to be a future date, which is likely a mistake or intended for a different property. Please verify the intended configuration (e.g., bootstrap, persistence, or a custom property).

Comment on lines +88 to 93
setDeletedHoldTypeIds((prev) => new Set([...prev, String(hold.hold_type?.id)]));
setLocallyAddedHolds((prev) => prev.filter((m) => m.hold_type?.id !== hold.hold_type?.id));
posthog.capture('hold removed from collection', { hold_name: hold.name, hold_id: hold.id, session_id: session_data?.id });
await authenticatedFetch(
`${API_URL}/gym/changeholdtosessioncollection/${session_data.id}/0/${hold.id}/`
);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

The handleDelete function performs an optimistic update by removing the hold from local state before the API call completes. If the authenticatedFetch fails, the UI will remain in an inconsistent state without the user knowing the deletion failed. Consider adding a try-catch block to roll back the local state changes if the network request fails.

Comment on lines 277 to +279
function renderHoldTree(parentId: string | null) {
return objects
.filter((o) => o.type === "hold" && o.parentId === parentId && o.url) // Only render objects with valid URLs
.filter((o) => o.type === "hold" && o.parentId === parentId && o.url)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Filtering the entire objects array inside the recursive renderHoldTree function results in O(N^2) complexity. As the number of placed holds grows, this will cause significant performance degradation during renders. It is recommended to pre-group objects by parentId (e.g., using a Map) before starting the recursive render process.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR refactors parts of the frontend to improve state handling and typing in the editor, updates analytics instrumentation, and removes react-helmet-async usage across the app.

Changes:

  • Removed react-helmet-async and replaced Helmet-based metadata with inline <title>/<meta> JSX in showcase pages.
  • Improved editor typing by centralizing shared types in store.ts and reducing any usage across editor utilities/components.
  • Adjusted editor UI state handling (memoization, local hold add/delete tracking) and updated PostHog event capture calls.

Reviewed changes

Copilot reviewed 24 out of 25 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
README.md Updates the “project purpose” video link.
frontend/vite.config.ts Changes dev proxy targets (removes /gym).
frontend/src/shared/auth/AuthProvider.tsx Adds eslint suppression for react-refresh export rule.
frontend/src/shared/analytics/posthog.ts Updates PostHog initialization options.
frontend/src/main.tsx Removes HelmetProvider wrapper.
frontend/src/features/showcase/HomePage.tsx Removes Helmet usage; adds inline title/meta/JSON-LD and updates PostHog capture call.
frontend/src/features/showcase/PartnersPage.tsx Removes Helmet usage; adds inline title/meta.
frontend/src/features/showcase/ContactPage.tsx Removes Helmet usage; adds inline title/meta.
frontend/src/features/editor/utils/useHoldAvailabilityValidation.tsx Replaces any with local types and tightens parsing/handling.
frontend/src/features/editor/utils/HandleLoadSession.tsx Introduces SessionData typing and tighter object typing/casts.
frontend/src/features/editor/utils/HandleAddHold.tsx Uses shared store types for hold models and callback typing.
frontend/src/features/editor/stubs/Hold360.tsx Improves typing for hold prop and removes any access.
frontend/src/features/editor/store.ts Adds shared TS interfaces/types (HoldType/HoldModel/SessionData/etc.).
frontend/src/features/editor/EditorApp.tsx Memoizes hold model derivation and adjusts query settings / event capture.
frontend/src/features/editor/components/useDragPreview.ts Minor typing/const cleanups and safer property checks.
frontend/src/features/editor/components/SidebarHoldsSection.tsx Refactors local hold list management and PostHog capture calls; adds typing.
frontend/src/features/editor/components/Sidebar.tsx Refactors hold model processing and current-holds tracking for modal.
frontend/src/features/editor/components/ModelViewer.tsx Refines material typing when extracting texture maps.
frontend/src/features/editor/components/MainCanvas.tsx Refactors wall-loading overlay logic and drag state derivations.
frontend/src/features/editor/components/HoldInspector.tsx Refines event listener typing.
frontend/src/features/editor/components/FileManager.tsx Tightens typing and updates PostHog calls for save flows.
frontend/src/features/editor/components/AddHoldModal.tsx Tightens stock typing and filtering logic.
frontend/package.json Removes react-helmet-async, bumps uuid.
frontend/package-lock.json Lockfile updates matching dependency changes.
.env.example Updates PostHog host example value.
Files not reviewed (1)
  • frontend/package-lock.json: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +424 to +430
// Sync batch size with current wall count — React's recommended pattern for resetting
// state when derived data changes (avoids setState-in-effect anti-pattern).
if (wallBatchSize !== wallObjects.length) {
setWallBatchSize(wallObjects.length);
setWallLoadCount(0);
setForceLoaded(false);
}
Comment thread frontend/vite.config.ts
proxy: (() => {
const backendUrl = process.env.BACKEND_URL ?? 'http://localhost:8000';
const targets = ['/api', '/gym', '/admin', '/static', '/media'];
const targets = ['/api', '/admin', '/static', '/media'];
Comment thread frontend/src/main.tsx
Comment on lines 12 to 19
createRoot(document.getElementById('root')!).render(
<StrictMode>
<HelmetProvider>
<QueryClientProvider client={queryClient}>
<AuthProvider>
<App />
</AuthProvider>
</QueryClientProvider>
</HelmetProvider>
<QueryClientProvider client={queryClient}>
<AuthProvider>
<App />
</AuthProvider>
</QueryClientProvider>
</StrictMode>
Comment on lines 41 to +53
<>
<Helmet>
<title>SetRsoft – Éditeur 3D Bouldering en ligne</title>
<meta name="description" content="Éditeur 3D virtuel pour créer vos blocs d'escalade bouldering. Simulation, routesetting, partage de séquences." />
<script type="application/ld+json">{JSON.stringify({
"@context": "https://schema.org",
"@type": "SoftwareApplication",
"name": "SetRsoft",
"applicationCategory": "SportsApplication",
"operatingSystem": "Web",
"description": "Éditeur 3D de blocs d'escalade bouldering en ligne. Routesetting virtuel.",
"url": "https://setrsoft.com",
"offers": { "@type": "Offer", "price": "0" }
})}</script>
</Helmet>
<title>SetRsoft – Éditeur 3D Bouldering en ligne</title>
<meta name="description" content="Éditeur 3D virtuel pour créer vos blocs d'escalade bouldering. Simulation, routesetting, partage de séquences." />
<script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify({
"@context": "https://schema.org",
"@type": "SoftwareApplication",
"name": "SetRsoft",
"applicationCategory": "SportsApplication",
"operatingSystem": "Web",
"description": "Éditeur 3D de blocs d'escalade bouldering en ligne. Routesetting virtuel.",
"url": "https://setrsoft.com",
"offers": { "@type": "Offer", "price": "0" }
}) }} />
Comment on lines 9 to 12
<>
<Helmet>
<title>Partenaires – SetRsoft</title>
<meta name="description" content="Salles d'escalade partenaires de SetRsoft, logiciel de routesetting 3D bouldering." />
</Helmet>
<title>Partenaires – SetRsoft</title>
<meta name="description" content="Salles d'escalade partenaires de SetRsoft, logiciel de routesetting 3D bouldering." />
<div className="flex flex-col items-center justify-center py-24 px-4 text-center animate-fade-in">
Comment on lines 9 to 12
<>
<Helmet>
<title>Contact – SetRsoft</title>
<meta name="description" content="Contactez l'équipe SetRsoft pour intégrer l'éditeur 3D dans votre salle d'escalade bouldering." />
</Helmet>
<title>Contact – SetRsoft</title>
<meta name="description" content="Contactez l'équipe SetRsoft pour intégrer l'éditeur 3D dans votre salle d'escalade bouldering." />
<div className="flex flex-col items-center justify-center py-24 px-4 text-center animate-fade-in">
Comment on lines 33 to 59
@@ -58,9 +55,7 @@ const Sidebar = ({
onClose={() => setAddHoldModalOpen(false)}
session_data={session_data}
onHoldAdded={handleHoldAddedFromModal}
currentHolds={
holdsSectionRef.current?.getCurrentHolds() || processedHoldModels
}
currentHolds={[...processedHoldModels, ...locallyAddedHolds]}
/>
Comment on lines 113 to 120
const handleDragStart =
(model: HoldModel) => (e: React.MouseEvent | React.TouchEvent) => {
e.preventDefault();
startDrag({
type: "hold",
url: model.hold_type.glb_url,
url: (model.hold_type.glb_url as string | undefined) ?? '',
customRotation: 0,
});
Comment on lines 44 to 63
@@ -58,7 +59,7 @@ export const useHandleLoadSession = (session_data: any) => {

const newObjects = wallToUse ? [wallToUse, ...sessionHolds] : sessionHolds;

setObjects(newObjects);
setObjects(newObjects as import("../store").PlacedObject[]);
setWallColors(data.wallColors || {});
@eloiberlinger1 eloiberlinger1 merged commit f65dd35 into prod May 11, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants