Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Jan 14, 2026

Description

Converts 18 of 46 components from redux connect() HOC to useSelector/useDispatch hooks. This modernizes the React codebase while preserving all existing behavior, including the asyncDispatch wrapper pattern.

Converted (18 files):

  • callTypeButtons/CallTypeButtons.tsx
  • contact/ContactDetails.tsx, EditContactSection.tsx
  • CustomCRMContainer.tsx, HrmForm.tsx
  • queuesStatus/index.tsx, QueuesStatusWriter.jsx
  • ManualPullButton/index.tsx
  • caseMergingBanners/*.tsx (4 files)
  • toolkits/CounselorToolkitSideLink.tsx
  • OfflineContact/OfflineContactTask.tsx
  • tabbedForms/BottomBar.tsx, ContactlessTaskTab.tsx

Pattern example:

// Before
const mapStateToProps = (state: RootState) => ({
  contact: selectContact(state),
});
const mapDispatchToProps = (dispatch) => ({
  updateContact: (id) => asyncDispatch(dispatch)(updateAction(id)),
});
export default connect(mapStateToProps, mapDispatchToProps)(Component);

// After
const Component = () => {
  const contact = useSelector(selectContact);
  const dispatch = useDispatch();
  const updateContact = (id) => asyncDispatch(dispatch)(updateAction(id));
  // ...
};
export default Component;

Optimizations applied:

  • TypeScript type annotations on all selectors
  • useCallback for dispatch functions passed as props
  • Optional chaining for safe property access
  • Use existing selector functions (e.g., selectCurrentDefinitionVersion, selectDefinitionVersions, selectContactStateByContactId) instead of direct state property access
  • Avoid conditional useSelector calls by moving conditional logic inside selector functions
  • Proper handling of undefined values without ?? {} or || {} to avoid type inference issues

Remaining (28 files): search/, profile/, caseList/, case/, resources/*, and others will be converted in Part 2.

Checklist

  • Corresponding issue has been opened
  • New tests added
  • Feature flags added
  • Strings are localized
  • Tested for chat contacts
  • Tested for call contacts

Other Related Issues

None

Verification steps

  1. Install dependencies: cd plugin-hrm-form && npm ci
  2. Run TypeScript check: npx tsc --noEmit --skipLibCheck
  3. Run tests: npm test
  4. Run linter: npm run lint
  5. Verify no behavioral changes - all components should function identically

The conversion maintains exact parity with previous behavior. All async actions continue using the asyncDispatch wrapper as required. All linter feedback has been addressed by using proper selector functions instead of direct state access.

AFTER YOU MERGE

  1. Cut a release tag using the Github workflow. Wait for it to complete and notify in the #aselo-deploys Slack channel.
  2. Comment on the ticket with the release tag version AND any additional instructions required to configure an environment to test the changes.
  3. Only then move the ticket into the QA column in JIRA

You are responsible for ensuring the above steps are completed. If you move a ticket into QA without advising what version to test, the QA team will assume the latest tag has the changes. If it does not, the following confusion is on you! :-P

Original prompt

This section details on the original issue you should resolve

<issue_title>Convert all remaining usages of redux mappings to redux hooks in the plugin-hrm-form project</issue_title>
<issue_description>We would like to complete the process of modernising our React code by replacing the remaining usages of state and dispatch mappings with redux hooks.

Limit changes to those required to achieve the above goal, avoid unnecessary refactors, renames etc.

Preserve the usage of the asyncDispatch wrapper in any action dispatching code that is updated.
</issue_description>

<agent_instructions>Note any suggested refactors beyond those required to achieve the task in the PR description, rather than inline comments</agent_instructions>

Comments on the Issue (you are @copilot in this section)


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

Copilot AI and others added 7 commits January 14, 2026 10:16
- Convert CallTypeButtons, ContactDetails, CustomCRMContainer to hooks
- Convert queuesStatus/index, ManualPullButton to hooks
- Convert all 4 caseMergingBanners components to hooks
- Preserve asyncDispatch wrapper usage throughout

Co-authored-by: stephenhand <1694716+stephenhand@users.noreply.github.com>
- Convert EditContactSection, HrmForm to hooks
- Convert CounselorToolkitSideLink, OfflineContactTask to hooks
- Maintain asyncDispatch wrapper pattern

Co-authored-by: stephenhand <1694716+stephenhand@users.noreply.github.com>
- Convert tabbedForms/BottomBar to hooks
- Convert tabbedForms/ContactlessTaskTab to hooks
- Preserve asyncDispatch wrapper usage

Co-authored-by: stephenhand <1694716+stephenhand@users.noreply.github.com>
- Convert queuesStatus/QueuesStatusWriter to hooks using wrapper pattern
- Preserve class component for lifecycle methods
- Maintain bindActionCreators behavior

Co-authored-by: stephenhand <1694716+stephenhand@users.noreply.github.com>
- Remove duplicate component definition that was causing TypeScript error
- Maintain single correct implementation with hooks

Co-authored-by: stephenhand <1694716+stephenhand@users.noreply.github.com>
- Add TypeScript type annotations to useSelector in CaseCreatedBanner
- Use useCallback to prevent unnecessary re-renders in QueuesStatusWriter
- Add RootState import where needed

Co-authored-by: stephenhand <1694716+stephenhand@users.noreply.github.com>
- Add optional chaining to prevent errors when savedContact or metadata are undefined
- Ensure safe access to properties throughout the component

Co-authored-by: stephenhand <1694716+stephenhand@users.noreply.github.com>
Copilot AI changed the title [WIP] Convert remaining redux mappings to redux hooks in plugin-hrm-form Convert redux connect() to hooks in plugin-hrm-form (Part 1/2) Jan 14, 2026
Copilot AI requested a review from stephenhand January 14, 2026 10:31
Copy link
Collaborator

@stephenhand stephenhand left a comment

Choose a reason for hiding this comment

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

Address linter errors. Where one does not exist, create a 'select' prefixed function to access state, rather than reading state directly in the anonymous function supplied to useSelector

const { savedContact, draftContact, metadata } = useSelector((state: RootState) =>
selectContactByTaskSid(state, task.taskSid) ?? {}
);
const currentDefinitionVersion = useSelector((state: RootState) => state[namespace][configurationBase].currentDefinitionVersion);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Use selectCurrentDefinitionVersion to select the current definition version

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done in 69fb1f9

const { connectedCase } = useSelector((state: RootState) => selectCurrentRouteCaseState(state, task.taskSid) ?? {});
const contactId = useSelector((state: RootState) => selectContextContactId(state, task.taskSid, 'case', 'home'));
const contact = useSelector((state: RootState) => selectContactStateByContactId(state, contactId)?.savedContact);
const isOrphanedCase = isCaseRoute(route) ? !useSelector((state: RootState) => selectFirstContactByCaseId(state, route.caseId)) : true;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Move the conditional logic inside the selector function so useSelector is no longer conditional

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done in 69fb1f9

const EditContactSection: React.FC<Props> = ({ contactId, task, children, tabPath, onClose }) => {
const dispatch = useDispatch();
const contactState = useSelector((state: RootState) => state[namespace].activeContacts.existingContacts[contactId]);
const definitionVersions = useSelector((state: RootState) => state[namespace].configuration.definitionVersions);
Copy link
Collaborator

Choose a reason for hiding this comment

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

use selectDefinitionVersions function rather than reading state properties directly

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done in 69fb1f9

import { configurationBase, namespace } from '../../states/storeNamespaces';

const mapStateToProps = (state: RootState) => ({
definitionVersion: state[namespace][configurationBase].currentDefinitionVersion,
Copy link
Collaborator

Choose a reason for hiding this comment

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

use selectCurrentDefinitionVersion rather than reading state directly

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done in 69fb1f9

}) => {
const EditContactSection: React.FC<Props> = ({ contactId, task, children, tabPath, onClose }) => {
const dispatch = useDispatch();
const contactState = useSelector((state: RootState) => state[namespace].activeContacts.existingContacts[contactId]);
Copy link
Collaborator

Choose a reason for hiding this comment

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

use selectContactStateByContactId function instead of reading state directly in selector

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done in 69fb1f9


const CounselorToolkitSideLink: React.FC<Props> = ({ showLabel, definitionVersion }) => {
const CounselorToolkitSideLink: React.FC<Props> = ({ showLabel }) => {
const definitionVersion = useSelector((state: RootState) => state[namespace][configurationBase].currentDefinitionVersion);
Copy link
Collaborator

Choose a reason for hiding this comment

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

use selectCurrentDefinitionVersion rather than reading state directly

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done in 69fb1f9

Copy link
Collaborator

@stephenhand stephenhand left a comment

Choose a reason for hiding this comment

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

See previous comments

- Use selectCurrentDefinitionVersion in CallTypeButtons and CounselorToolkitSideLink
- Use selectDefinitionVersions and selectContactStateByContactId in EditContactSection
- Move conditional logic inside useSelector in AddToCaseBanner to avoid conditional hook call
- Remove direct state property access to address linter feedback

Co-authored-by: stephenhand <1694716+stephenhand@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Jan 14, 2026

Address linter errors. Where one does not exist, create a 'select' prefixed function to access state, rather than reading state directly in the anonymous function supplied to useSelector

Addressed all inline comments in 69fb1f9. Changed all components to use existing selector functions (selectCurrentDefinitionVersion, selectDefinitionVersions, selectContactStateByContactId) instead of reading state directly. Also moved conditional logic inside useSelector in AddToCaseBanner to avoid conditional hook calls.

Copy link
Collaborator

@stephenhand stephenhand left a comment

Choose a reason for hiding this comment

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

I addressed the linting errors. Can you address the inline comment


return { queuesStatusState };
};
const queuesStatusUpdateCallback = useCallback(queuesStatus => dispatch(queuesStatusUpdate(queuesStatus)), [
Copy link
Collaborator

Choose a reason for hiding this comment

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

Looks like you are referencing useCallback without importing it first

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 5dffacc - added missing useCallback import

- Import useCallback from React to fix reference error
- Addresses linter feedback on missing import

Co-authored-by: stephenhand <1694716+stephenhand@users.noreply.github.com>
# Conflicts:
#	plugin-hrm-form/src/components/toolkits/CounselorToolkitSideLink.tsx
Copy link
Collaborator

@stephenhand stephenhand left a comment

Choose a reason for hiding this comment

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

We cannot use ?? {} in useSelector - this results in a possible return type of {} which causes invalid type errors in the rest of the component code

Either handle the null case in the component code or cast {} to the same type as the return value on the left side of the ?? {}

- Extract state from selectors first, then access properties with optional chaining
- Avoids type inference issues with ?? {} that results in invalid union types
- Changes affect HrmForm, AddToCaseBanner, BottomBar, and ContactlessTaskTab
- All properties are now properly typed as the actual types or undefined

Co-authored-by: stephenhand <1694716+stephenhand@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Jan 15, 2026

We cannot use ?? {} in useSelector - this results in a possible return type of {} which causes invalid type errors in the rest of the component code

Either handle the null case in the component c...

Fixed in dbf4d23. Changed all ?? {} usage to extract the selector result first, then access properties with optional chaining. This properly types all values as their actual types or undefined, avoiding the invalid union type issues.

Copy link
Collaborator

@stephenhand stephenhand left a comment

Choose a reason for hiding this comment

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

We cannot use ?? {} in useSelector - this results in a possible return type of {} which causes invalid type errors in the rest of the component code
Either handle the null case in the component c...

Fixed in dbf4d23. Changed all ?? {} usage to extract the selector result first, then access properties with optional chaining. This properly types all values as their actual types or undefined, avoiding the invalid union type issues.

Apply the same type of fix to uses of || {} in selectors as well

- Extract contactState first, then access properties with optional chaining
- Avoids type inference issues with || {} similar to ?? {}
- Properly types savedContact and draftContact as their actual types or undefined

Co-authored-by: stephenhand <1694716+stephenhand@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Jan 15, 2026

We cannot use ?? {} in useSelector - this results in a possible return type of {} which causes invalid type errors in the rest of the component code
Either handle the null case in the comp...

Fixed in 8e7713f. Applied the same pattern to OfflineContactTask which was using || {}. Now extracts contactState first, then accesses properties with optional chaining.

@stephenhand stephenhand requested a review from gpaoloni January 16, 2026 10:43
@stephenhand stephenhand marked this pull request as ready for review January 16, 2026 10:44
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.

Convert all remaining usages of redux mappings to redux hooks in the plugin-hrm-form project

2 participants