Skip to content
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

Remove all class components and replace them functional components using hooks #6376

Closed
2 of 4 tasks
kschiffer opened this issue Jul 5, 2023 · 8 comments
Closed
2 of 4 tasks
Assignees
Labels
in progress We're working on it technical debt Not necessarily broken, but could be done better/cleaner ui/web This is related to a web interface
Milestone

Comments

@kschiffer
Copy link
Member

kschiffer commented Jul 5, 2023

Summary

To follow the latest best practices and simplify implementations, we should work to replace any React class component with a functional component. We already converted many in the last years and I replaced many more as part of #6327 but there is still a substantial amount left.

Current Situation

Many older components still use the class-based approach, which is becoming increasingly cumbersome as it often causes incompatibilities with upgraded dependencies, such as react-router v6 but also overall constitutes a code smell. Latest react versions clearly state functional components as the preferred modus.

Why do we need this? Who uses it, and when?

We need this to address technical debt and to make our codebase more future-proof. It's also annoying to have to convert classes on the spot while working on other features, as it takes additional time, poses potential error risks and dilutes the diff which makes reviewing harder.

Proposed Implementation

Let's make sure we do this in a consistent manner. Here's are the guidelines I followed when converting components:

  1. Class components that make use of withRequest()-HOC to fetch initial data should be split into two functional components (outer and inner, arrow functions, the outer should keep the original name), so that <RequireRequest requestAction={exampleActionCreator(param1, param2)} /> can be run in the outer function to ensure that all required data will be available in the inner class when using useSelect.
  2. Class components that make use of connect()-HOC should be refactored to use useSelect() instead (in the inner component)
  3. Class components that make use of withBreadcrumbs()-HOC should use useBreadcrumbs('id.path', <Breadcrumbs … />)-hook instead (same signature as withBreadcrumbs)
  4. Class components that make use of withFeatureRequirement()-HOC for feature flags should use <Require featureCheck={mayDoAction} otherwise={{ redirect: '/example-route' }} /> instead
  5. Classes using Routes should use relative paths (react router v6)
  6. All hooks and handlers should be wrapped in useCallback()
  7. Unused proptypes should be removed

Contributing

  • I can help by doing more research.
  • I can help by implementing the feature after the proposal above is approved.
  • I can help by testing the feature before it's released.

Code of Conduct

@kschiffer kschiffer added technical debt Not necessarily broken, but could be done better/cleaner ui/web This is related to a web interface needs/triage We still need to triage this labels Jul 5, 2023
@kschiffer
Copy link
Member Author

Once we have all class components removed, we'll also be able to refactor our redux-actions to get rid of REQUEST|SUCCESS|FAILURE actions, as we can fully switch to promisified dispatches. This will make action definition a lot simpler, readable, and maintainable.

@KrishnaIyer KrishnaIyer added this to the v3.27.0 milestone Jul 12, 2023
@KrishnaIyer KrishnaIyer removed the needs/triage We still need to triage this label Jul 12, 2023
@KrishnaIyer
Copy link
Member

As discussed offline, @ryaplots will take a crack at this this milestone to see how far we can get.

@kschiffer
Copy link
Member Author

kschiffer commented Jul 24, 2023

As of today, the remaining class components are:

  • /pkg/webui/components/tabs/tab/index.js
  • /pkg/webui/components/code-editor/index.js
  • /pkg/webui/components/radio-button/group/index.js
  • /pkg/webui/components/radio-button/radio.js
  • /pkg/webui/components/pagination/index.js
  • /pkg/webui/components/form/field/stories/shared.js
  • /pkg/webui/components/input/index.js
  • /pkg/webui/components/input/toggled.js
  • /pkg/webui/components/input/stories/shared.js
  • /pkg/webui/components/input/byte.js
  • /pkg/webui/components/progress-bar/index.js
  • /pkg/webui/components/toast/index.js
  • /pkg/webui/components/checkbox/group/index.js
  • /pkg/webui/components/checkbox/checkbox.js
  • /pkg/webui/components/navigation/side/index.js
  • /pkg/webui/components/navigation/side/list/index.js
  • /pkg/webui/components/submit-button/index.js
  • /pkg/webui/components/rights-group/index.js
  • /pkg/webui/components/spinner/index.js
  • /pkg/webui/components/safe-inspector/index.js
  • /pkg/webui/components/file-input/index.js
  • /pkg/webui/components/map/widget/index.js
  • /pkg/webui/components/button/modal-button/index.js
  • /pkg/webui/components/key-value-map/index.js
  • /pkg/webui/components/key-value-map/entry.js
  • /pkg/webui/components/table/sort-button/index.js
  • /pkg/webui/components/table/index.js
  • /pkg/webui/components/table/row/index.js
  • /pkg/webui/components/table/table/index.js
  • /pkg/webui/components/profile-dropdown/index.js
  • /pkg/webui/components/breadcrumbs/context.js
  • /pkg/webui/components/tag/group/index.js
  • /pkg/webui/lib/components/date-time/index.js
  • /pkg/webui/lib/components/error-view.js (error boundary cannot be a functional component)
  • /pkg/webui/lib/components/with-computed-props.js
  • /pkg/webui/lib/components/intl-helmet.js
  • /pkg/webui/lib/components/animation/index.js
  • /pkg/webui/lib/components/env.js (removed)
  • /pkg/webui/lib/components/init.js
  • /pkg/webui/lib/components/with-request.js
  • /pkg/webui/console/components/payload-formatters-form/index.js
  • /pkg/webui/console/components/webhook-template-form/index.js
  • /pkg/webui/console/components/user-data-form/index.js
  • /pkg/webui/console/components/webhook-form/index.js
  • /pkg/webui/console/components/pubsub-form/index.js
  • /pkg/webui/console/components/device-map/index.js
  • /pkg/webui/console/components/gateway-map/index.js
  • /pkg/webui/console/components/events/error-boundary.js
  • /pkg/webui/console/components/location-form/index.js
  • /pkg/webui/console/components/join-eui-prefixes-input/index.js (removed)
  • /pkg/webui/console/lib/components/require.js
  • /pkg/webui/console/lib/components/with-feature-requirement.js (removed)
  • /pkg/webui/console/containers/gateway-connection/gateway-connection.js
  • /pkg/webui/console/containers/gateway-connection/gateway-connection-reactor.js (removed)
  • /pkg/webui/console/containers/log-back-in-modal/index.js
  • /pkg/webui/console/containers/packet-broker-networks-table/index.js
  • /pkg/webui/console/containers/fetch-select/index.js
  • /pkg/webui/console/containers/device-importer/index.js

Via find ./pkg/webui -name "*.js" ! -name "story.js" -exec grep -l 'class .* extends' {} \;

All HOCs should just be removed if they're not used anymore. Otherwise a hook version should be written for them so usage can be replaced properly.

@kschiffer kschiffer changed the title Remove all class components and replace them with hooks Remove all class components and replace them functional components using hooks Jul 24, 2023
@kschiffer
Copy link
Member Author

kschiffer commented Jul 24, 2023

We're also quite far already with removing all withRequest instances.

Last standing are:

  • ./pkg/webui/console/containers/application-title-section/connect.js
  • ./pkg/webui/console/containers/gateway-title-section/connect.js
  • ./pkg/webui/console/containers/organization-title-section/connect.js

Once we've refactored these, we can remove withRequest entirely and with it all fetching, error store logic.

@ryaplots
Copy link
Contributor

ryaplots commented Aug 3, 2023

We also have some functional components that still use connect(:

  • ./pkg/webui/lib/components/full-view-error/connect.js Error boundary has to be in class component
  • ./pkg/webui/lib/components/full-view-error/index.js Error boundary has to be in class component
  • ./pkg/webui/account/containers/collaborators-table/index.js
  • ./pkg/webui/console/components/uplink-form/connect.js
  • ./pkg/webui/console/components/uplink-form/index.js
  • ./pkg/webui/console/components/downlink-form/connect.js
  • ./pkg/webui/console/components/downlink-form/index.js
  • ./pkg/webui/console/containers/gateway-location-form/connect.js
  • ./pkg/webui/console/containers/gateway-location-form/index.js
  • ./pkg/webui/console/containers/application-title-section/connect.js
  • ./pkg/webui/console/containers/application-title-section/index.js
  • ./pkg/webui/console/containers/application-events/index.js
  • ./pkg/webui/console/containers/gateway-title-section/connect.js
  • ./pkg/webui/console/containers/gateway-title-section/index.js
  • ./pkg/webui/console/containers/device-profile-section/device-selection/brand-select/connect.js
  • ./pkg/webui/console/containers/device-profile-section/device-selection/brand-select/index.js
  • ./pkg/webui/console/containers/device-profile-section/device-selection/model-select/connect.js
  • ./pkg/webui/console/containers/device-profile-section/device-selection/model-select/index.js
  • ./pkg/webui/console/containers/device-profile-section/device-selection/fw-version-select/connect.js
  • ./pkg/webui/console/containers/device-profile-section/device-selection/fw-version-select/index.js
  • ./pkg/webui/console/containers/device-profile-section/device-selection/hw-version-select/connect.js
  • ./pkg/webui/console/containers/device-profile-section/device-selection/hw-version-select/index.js
  • ./pkg/webui/console/containers/device-profile-section/device-card/connect.js
  • ./pkg/webui/console/containers/device-profile-section/device-card/index.js
  • ./pkg/webui/console/containers/applications-table/index.js
  • ./pkg/webui/console/containers/gateway-connection/connect.js
  • ./pkg/webui/console/containers/gateway-connection/index.js
  • ./pkg/webui/console/containers/device-events/index.js
  • ./pkg/webui/console/containers/device-title-section/connect.js
  • ./pkg/webui/console/containers/device-title-section/index.js
  • ./pkg/webui/console/containers/organization-events/connect.js
  • ./pkg/webui/console/containers/organization-events/index.js
  • ./pkg/webui/console/containers/dev-addr-input/index.js
  • ./pkg/webui/console/containers/organization-title-section/connect.js
  • ./pkg/webui/console/containers/organization-title-section/index.js
  • ./pkg/webui/console/containers/owners-select/connect.js
  • ./pkg/webui/console/containers/owners-select/index.js
  • ./pkg/webui/console/containers/gateway-events/index.js

@KrishnaIyer
Copy link
Member

Rolling item in progress. Moved to v3.27.2

@ryaplots
Copy link
Contributor

Referencing this comment.
Checkbox group and radio button group need a proper refactoring.

@kschiffer
Copy link
Member Author

Closing this now as completed. The remaining two components can be refactored later. What's important now is that the vast majority of components are refactored and we can now look into some more general restructuring of our codebase such as refactoring fetching and error logic which is now done completely based on promisified dispatches rather than store artifacts.

Thanks @ryaplots and @PavelJankoski for the hard work on this. This made our codebase a whole lot more future-proof!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in progress We're working on it technical debt Not necessarily broken, but could be done better/cleaner ui/web This is related to a web interface
Projects
None yet
Development

No branches or pull requests

4 participants