Skip to content

Refactor action bar components to React functional components #8754

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 3 commits into
base: master
Choose a base branch
from

Conversation

hotzenklotz
Copy link
Member

This PR converts several class components in the action bar to React functional components.

The following components were refactored:

  • DatasetPositionView
  • SaveButton
  • TracingActionsView
  • ViewModesView

URL of deployed dev instance (used for testing):

  • https://___.webknossos.xyz

Steps to test:

  • Check that action bar components still do their job

Issues:


(Please delete unneeded items, merge only when none are left open)

  • Added changelog entry (create a $PR_NUMBER.md file in unreleased_changes or use ./tools/create-changelog-entry.py)
  • Added migration guide entry if applicable (edit the same file as for the changelog)
  • Updated documentation if applicable
  • Adapted wk-libs python client if relevant API parts change
  • Removed dev-only changes like prints and application.conf edits
  • Considered common edge cases
  • Needs datastore update after deployment

This commit converts several class components in the action bar to functional components, enhancing code readability and maintainability. The following components were refactored:
- DatasetPositionView
- SaveButton
- TracingActionsView
- ViewModesView
@hotzenklotz hotzenklotz self-assigned this Jul 4, 2025
@hotzenklotz hotzenklotz requested a review from philippotto July 4, 2025 13:25
@hotzenklotz hotzenklotz changed the title Refactor action bar components to functional components using hooks Refactor action bar components to React functional components Jul 4, 2025
Copy link
Contributor

coderabbitai bot commented Jul 7, 2025

📝 Walkthrough

Walkthrough

Several React components in the frontend viewer's action bar were refactored from class-based components using Redux connect to functional components utilizing React hooks and custom selector hooks. State selection and dispatching are now handled directly within components, and all internal state and event handlers were adapted to the functional paradigm. No core logic or UI behavior was altered.

Changes

Files Change Summary
frontend/javascripts/viewer/view/action-bar/dataset_position_view.tsx Refactored from class-based PureComponent with Redux connect to a functional component using hooks.
frontend/javascripts/viewer/view/action-bar/save_button.tsx Refactored from class-based component with Redux connect to a functional component using hooks.
frontend/javascripts/viewer/view/action-bar/tracing_actions_view.tsx Refactored from class-based component with Redux connect to a functional component using hooks.
frontend/javascripts/viewer/view/action-bar/view_modes_view.tsx Refactored from class-based component with Redux connect to a functional component using hooks.

Suggested reviewers

  • MichaelBuessemeyer
  • philippotto

Poem

🐇✨
Hooks now hop where classes stood,
Redux connect has left the wood.
Components fresh, concise, and neat,
With useWkSelector, can't be beat.
The action bar feels light and spry—
Hooray for code that loves to fly!
✨🌱

✨ Finishing Touches
  • 📝 Generate Docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (4)
frontend/javascripts/viewer/view/action-bar/view_modes_view.tsx (1)

57-69: Memoize MENU_ITEMS to prevent unnecessary re-renders.

The menu items are recreated on every render, which could cause performance issues.

+import { useCallback, useMemo } from "react";

-const MENU_ITEMS: MenuProps["items"] = [
+const MENU_ITEMS: MenuProps["items"] = useMemo(() => [
   {
     key: "1",
     type: "group",
     label: "Select View Mode",
     children: ViewModeValues.map((mode) => ({
       label: Utils.capitalize(mode),
       key: mode,
       disabled: isDisabled(mode),
       icon: <span style={{ marginRight: 8 }}>{VIEW_MODE_TO_ICON[mode]}</span>,
     })),
   },
-];
+], [allowedModes]);

Also update the isDisabled function:

-const isDisabled = (mode: ViewMode) => {
+const isDisabled = useCallback((mode: ViewMode) => {
   return !allowedModes.includes(mode);
-};
+}, [allowedModes]);
frontend/javascripts/viewer/view/action-bar/dataset_position_view.tsx (2)

42-60: Memoize event handlers to prevent unnecessary re-renders.

The event handlers are recreated on every render, which could impact performance.

+import { useCallback } from "react";

-const copyPositionToClipboard = async () => {
+const copyPositionToClipboard = useCallback(async () => {
   const position = V3.floor(getPosition(flycam)).join(", ");
   await navigator.clipboard.writeText(position);
   Toast.success("Position copied to clipboard");
-};
+}, [flycam]);

-const copyRotationToClipboard = async () => {
+const copyRotationToClipboard = useCallback(async () => {
   const rotation = V3.round(getRotation(flycam)).join(", ");
   await navigator.clipboard.writeText(rotation);
   Toast.success("Rotation copied to clipboard");
-};
+}, [flycam]);

-const handleChangePosition = (position: Vector3) => {
+const handleChangePosition = useCallback((position: Vector3) => {
   Store.dispatch(setPositionAction(position));
-};
+}, []);

-const handleChangeRotation = (rotation: Vector3) => {
+const handleChangeRotation = useCallback((rotation: Vector3) => {
   Store.dispatch(setRotationAction(rotation));
-};
+}, []);

62-91: Consider memoizing the position bounds checking function.

The isPositionOutOfBounds function contains complex logic and is recreated on every render.

-const isPositionOutOfBounds = (position: Vector3) => {
+const isPositionOutOfBounds = useCallback((position: Vector3) => {
   const { min: datasetMin, max: datasetMax } = getDatasetExtentInVoxel(dataset);

   const isPositionOutOfBounds = (min: Vector3, max: Vector3) =>
     position[0] < min[0] ||
     position[1] < min[1] ||
     position[2] < min[2] ||
     position[0] >= max[0] ||
     position[1] >= max[1] ||
     position[2] >= max[2];

   const isOutOfDatasetBounds = isPositionOutOfBounds(datasetMin, datasetMax);
   let isOutOfTaskBounds = false;

   if (task?.boundingBox) {
     const bbox = task.boundingBox;
     const bboxMax = [
       bbox.topLeft[0] + bbox.width,
       bbox.topLeft[1] + bbox.height,
       bbox.topLeft[2] + bbox.depth,
     ];
     // @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'number[]' is not assignable to p... Remove this comment to see the full error message
     isOutOfTaskBounds = isPositionOutOfBounds(bbox.topLeft, bboxMax);
   }

   return {
     isOutOfDatasetBounds,
     isOutOfTaskBounds,
   };
-};
+}, [dataset, task]);
frontend/javascripts/viewer/view/action-bar/tracing_actions_view.tsx (1)

524-540: Memoize event handlers to optimize performance.

Several event handlers are recreated on every render. Consider memoizing them with useCallback.

-const handleSave = async (event?: React.MouseEvent<HTMLButtonElement>) => {
+const handleSave = useCallback(async (event?: React.MouseEvent<HTMLButtonElement>) => {
   if (event != null) {
     // @ts-expect-error ts-migrate(2339) FIXME: Property 'blur' does not exist on type 'EventTarge... Remove this comment to see the full error message
     event.target.blur();
   }

   Model.forceSave();
-};
+}, []);

-const handleUndo = () => dispatchUndoAsync(Store.dispatch);
-const handleRedo = () => dispatchRedoAsync(Store.dispatch);
+const handleUndo = useCallback(() => dispatchUndoAsync(Store.dispatch), []);
+const handleRedo = useCallback(() => dispatchRedoAsync(Store.dispatch), []);

-const handleCopyToAccount = async () => {
+const handleCopyToAccount = useCallback(async () => {
   // duplicates the annotation in the current user account
   const newAnnotation = await duplicateAnnotation(annotationId, annotationType);
   location.href = `/annotations/${newAnnotation.id}`;
-};
+}, [annotationId, annotationType]);
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 531a578 and 929fc74.

📒 Files selected for processing (4)
  • frontend/javascripts/viewer/view/action-bar/dataset_position_view.tsx (3 hunks)
  • frontend/javascripts/viewer/view/action-bar/save_button.tsx (4 hunks)
  • frontend/javascripts/viewer/view/action-bar/tracing_actions_view.tsx (8 hunks)
  • frontend/javascripts/viewer/view/action-bar/view_modes_view.tsx (2 hunks)
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
Learnt from: philippotto
PR: scalableminds/webknossos#8542
File: frontend/javascripts/viewer/model/sagas/volumetracing_saga.tsx:0-0
Timestamp: 2025-05-30T12:36:09.930Z
Learning: The WebKnossos frontend follows Redux immutability principles where reducers act immutably and create new state objects when changes occur. This makes reference equality checks (like `prevVolumeTracing.segments !== volumeTracing.segments`) reliable and efficient for detecting actual state changes, allowing memoization to work correctly.
frontend/javascripts/viewer/view/action-bar/view_modes_view.tsx (1)
Learnt from: philippotto
PR: scalableminds/webknossos#8542
File: frontend/javascripts/viewer/model/sagas/volumetracing_saga.tsx:0-0
Timestamp: 2025-05-30T12:36:09.930Z
Learning: The WebKnossos frontend follows Redux immutability principles where reducers act immutably and create new state objects when changes occur. This makes reference equality checks (like `prevVolumeTracing.segments !== volumeTracing.segments`) reliable and efficient for detecting actual state changes, allowing memoization to work correctly.
frontend/javascripts/viewer/view/action-bar/dataset_position_view.tsx (2)
Learnt from: philippotto
PR: scalableminds/webknossos#8542
File: frontend/javascripts/viewer/model/sagas/volumetracing_saga.tsx:0-0
Timestamp: 2025-05-30T12:36:09.930Z
Learning: The WebKnossos frontend follows Redux immutability principles where reducers act immutably and create new state objects when changes occur. This makes reference equality checks (like `prevVolumeTracing.segments !== volumeTracing.segments`) reliable and efficient for detecting actual state changes, allowing memoization to work correctly.
Learnt from: MichaelBuessemeyer
PR: scalableminds/webknossos#8485
File: frontend/javascripts/oxalis/model/accessors/dataset_layer_transformation_accessor.ts:384-392
Timestamp: 2025-04-01T09:45:17.527Z
Learning: The function `isRotationAndMirrorMaybeOnly` in the dataset_layer_transformation_accessor.ts is intentionally designed to allow mirroring transformations (negative scale values). It uses the length comparison (`scale.length() === NON_SCALED_VECTOR.length()`) rather than component equality to permit mirrored axes while ensuring the overall scale magnitude remains the same.
frontend/javascripts/viewer/view/action-bar/tracing_actions_view.tsx (2)
Learnt from: philippotto
PR: scalableminds/webknossos#8542
File: frontend/javascripts/viewer/model/sagas/volumetracing_saga.tsx:0-0
Timestamp: 2025-05-30T12:36:09.930Z
Learning: The WebKnossos frontend follows Redux immutability principles where reducers act immutably and create new state objects when changes occur. This makes reference equality checks (like `prevVolumeTracing.segments !== volumeTracing.segments`) reliable and efficient for detecting actual state changes, allowing memoization to work correctly.
Learnt from: knollengewaechs
PR: scalableminds/webknossos#8321
File: frontend/javascripts/components/slider.tsx:0-0
Timestamp: 2025-02-05T20:16:45.712Z
Learning: When tracking focus state that needs to be accessed by native event listeners (e.g., wheel events), use `useRef` instead of `useState` to ensure the event handlers always have access to the current value. This is because event listeners added via `addEventListener` would capture the initial state value if `useState` were used.
🧬 Code Graph Analysis (1)
frontend/javascripts/viewer/view/action-bar/save_button.tsx (2)
frontend/javascripts/libs/react_hooks.ts (1)
  • useWkSelector (292-294)
frontend/javascripts/components/fast_tooltip.tsx (1)
  • FastTooltip (54-123)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: backend-tests
  • GitHub Check: frontend-tests
  • GitHub Check: build-smoketest-push
🔇 Additional comments (3)
frontend/javascripts/viewer/view/action-bar/save_button.tsx (1)

41-85: Well-executed refactor to functional component!

The conversion preserves all functionality correctly:

  • State management properly converted to hooks
  • Polling mechanism correctly implemented with useEffect
  • Memoization with reuseInstanceOnEquality maintained
  • Clean dependency arrays
frontend/javascripts/viewer/view/action-bar/dataset_position_view.tsx (1)

152-152: Verify the icon change for the rotation copy button.

The icon was changed from a rotated pushpin to a reload icon. Please confirm this UI change is intentional.

frontend/javascripts/viewer/view/action-bar/tracing_actions_view.tsx (1)

490-522: Excellent implementation of timeout management with useRef!

The timeout management correctly uses useRef to ensure the timeout can be properly cleared and accessed within the effect. This follows the best practice noted in the retrieved learnings for native event handling.

Comment on lines 36 to 51
const handleChange: MenuProps["onClick"] = (args) => {
if (!ViewModeValues.includes(args.key as ViewMode)) {
return;
}
const mode = args.key as ViewMode;
// If we switch back from any arbitrary mode we stop recording.
// This prevents that when the user switches back to any arbitrary mode,
// a new node is instantly created at the screen's center.
if (
constants.MODES_ARBITRARY.includes(this.props.viewMode) &&
mode === constants.MODE_PLANE_TRACING
) {
this.props.onChangeFlightmodeRecording(false);
if (constants.MODES_ARBITRARY.includes(viewMode) && mode === constants.MODE_PLANE_TRACING) {
onChangeFlightmodeRecording(false);
}

Store.dispatch(setViewModeAction(mode));
// @ts-expect-error ts-migrate(2339) FIXME: Property 'blur' does not exist on type 'Element'.
args.domEvent.target.blur();
};
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Use consistent dispatch method throughout the component.

The function uses Store.dispatch directly instead of the dispatch hook obtained from useDispatch(). This breaks the pattern established by the refactor.

Apply this diff to fix the inconsistent dispatch usage:

-const handleChange: MenuProps["onClick"] = (args) => {
+const handleChange: MenuProps["onClick"] = useCallback((args) => {
   if (!ViewModeValues.includes(args.key as ViewMode)) {
     return;
   }
   const mode = args.key as ViewMode;
   // If we switch back from any arbitrary mode we stop recording.
   // This prevents that when the user switches back to any arbitrary mode,
   // a new node is instantly created at the screen's center.
   if (constants.MODES_ARBITRARY.includes(viewMode) && mode === constants.MODE_PLANE_TRACING) {
     onChangeFlightmodeRecording(false);
   }

-  Store.dispatch(setViewModeAction(mode));
+  dispatch(setViewModeAction(mode));
   // @ts-expect-error ts-migrate(2339) FIXME: Property 'blur' does not exist on type 'Element'.
   args.domEvent.target.blur();
-};
+}, [viewMode, onChangeFlightmodeRecording, dispatch]);
📝 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
const handleChange: MenuProps["onClick"] = (args) => {
if (!ViewModeValues.includes(args.key as ViewMode)) {
return;
}
const mode = args.key as ViewMode;
// If we switch back from any arbitrary mode we stop recording.
// This prevents that when the user switches back to any arbitrary mode,
// a new node is instantly created at the screen's center.
if (
constants.MODES_ARBITRARY.includes(this.props.viewMode) &&
mode === constants.MODE_PLANE_TRACING
) {
this.props.onChangeFlightmodeRecording(false);
if (constants.MODES_ARBITRARY.includes(viewMode) && mode === constants.MODE_PLANE_TRACING) {
onChangeFlightmodeRecording(false);
}
Store.dispatch(setViewModeAction(mode));
// @ts-expect-error ts-migrate(2339) FIXME: Property 'blur' does not exist on type 'Element'.
args.domEvent.target.blur();
};
const handleChange: MenuProps["onClick"] = useCallback((args) => {
if (!ViewModeValues.includes(args.key as ViewMode)) {
return;
}
const mode = args.key as ViewMode;
// If we switch back from any arbitrary mode we stop recording.
// This prevents that when the user switches back to any arbitrary mode,
// a new node is instantly created at the screen's center.
if (constants.MODES_ARBITRARY.includes(viewMode) && mode === constants.MODE_PLANE_TRACING) {
onChangeFlightmodeRecording(false);
}
dispatch(setViewModeAction(mode));
// @ts-expect-error ts-migrate(2339) FIXME: Property 'blur' does not exist on type 'Element'.
args.domEvent.target.blur();
}, [viewMode, onChangeFlightmodeRecording, dispatch]);
🤖 Prompt for AI Agents
In frontend/javascripts/viewer/view/action-bar/view_modes_view.tsx between lines
36 and 51, replace the direct use of Store.dispatch with the dispatch function
obtained from the useDispatch hook to maintain consistent dispatch usage
throughout the component. Locate where Store.dispatch(setViewModeAction(mode))
is called and change it to dispatch(setViewModeAction(mode)) after ensuring
dispatch is properly initialized via useDispatch at the component level.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
frontend/javascripts/viewer/view/action-bar/tracing_actions_view.tsx (1)

544-544: Consider using hook-selected state for consistency.

The handlers are still using Store.getState() directly instead of hook-selected state, which is inconsistent with the overall refactor approach.

For consistency with the functional component pattern, consider adding these state selections at the component level:

+ const annotation = useWkSelector((state) => state.annotation);
+ const dataset = useWkSelector((state) => state.dataset);

Then update the handlers to use the hook-selected state:

- const { annotation: sandboxAnnotation, dataset } = Store.getState();
+ const sandboxAnnotation = annotation;
// ... and similar for other Store.getState() calls

Also applies to: 561-561

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 929fc74 and b75db00.

📒 Files selected for processing (2)
  • frontend/javascripts/viewer/view/action-bar/tracing_actions_view.tsx (8 hunks)
  • frontend/javascripts/viewer/view/action-bar/view_modes_view.tsx (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • frontend/javascripts/viewer/view/action-bar/view_modes_view.tsx
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: philippotto
PR: scalableminds/webknossos#8542
File: frontend/javascripts/viewer/model/sagas/volumetracing_saga.tsx:0-0
Timestamp: 2025-05-30T12:36:09.930Z
Learning: The WebKnossos frontend follows Redux immutability principles where reducers act immutably and create new state objects when changes occur. This makes reference equality checks (like `prevVolumeTracing.segments !== volumeTracing.segments`) reliable and efficient for detecting actual state changes, allowing memoization to work correctly.
frontend/javascripts/viewer/view/action-bar/tracing_actions_view.tsx (3)
Learnt from: philippotto
PR: scalableminds/webknossos#8542
File: frontend/javascripts/viewer/model/sagas/volumetracing_saga.tsx:0-0
Timestamp: 2025-05-30T12:36:09.930Z
Learning: The WebKnossos frontend follows Redux immutability principles where reducers act immutably and create new state objects when changes occur. This makes reference equality checks (like `prevVolumeTracing.segments !== volumeTracing.segments`) reliable and efficient for detecting actual state changes, allowing memoization to work correctly.
Learnt from: knollengewaechs
PR: scalableminds/webknossos#8321
File: frontend/javascripts/components/slider.tsx:0-0
Timestamp: 2025-02-05T20:16:45.712Z
Learning: When tracking focus state that needs to be accessed by native event listeners (e.g., wheel events), use `useRef` instead of `useState` to ensure the event handlers always have access to the current value. This is because event listeners added via `addEventListener` would capture the initial state value if `useState` were used.
Learnt from: dieknolle3333
PR: scalableminds/webknossos#8168
File: frontend/javascripts/oxalis/model/sagas/proofread_saga.ts:1039-1039
Timestamp: 2024-11-22T17:18:04.217Z
Learning: In `frontend/javascripts/oxalis/model/sagas/proofread_saga.ts`, when calling `getMagInfo`, the use of `volumeTracingLayer.resolutions` is intentional and should not be changed to `volumeTracingLayer.mags`.
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: frontend-tests
  • GitHub Check: backend-tests
  • GitHub Check: build-smoketest-push
🔇 Additional comments (5)
frontend/javascripts/viewer/view/action-bar/tracing_actions_view.tsx (5)

45-47: LGTM! Proper imports for functional component refactor.

The imports correctly include the necessary hooks (useWkSelector, useCallback, useEffect, useState) for the functional component implementation.


467-489: Excellent hook-based state selection implementation.

All state selections are properly implemented using useWkSelector hook instead of connect and mapStateToProps, following React hooks best practices.


491-523: Well-implemented state management with hooks.

The useState and useEffect hooks properly replace the class component lifecycle methods. The timeout cleanup in the effect's return function correctly prevents memory leaks.


600-668: Excellent useCallback implementation addresses previous review feedback.

The getTracingViewModals function has been properly converted to a useCallback hook with comprehensive dependencies, and now uses hook-selected viewMode state instead of direct Store access. This resolves the previous review comment about direct Store.getState() usage.


783-801: Clean functional component return structure.

The JSX structure maintains the same UI behavior while properly using the hook-selected state and local handlers. The component follows React functional component best practices.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant