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

fix(tree-select): [tree-select] fix deleteTag not working #2543

Draft
wants to merge 1 commit into
base: dev
Choose a base branch
from

Conversation

kagol
Copy link
Member

@kagol kagol commented Nov 22, 2024

PR

PR Checklist

Please check if your PR fulfills the following requirements:

  • The commit message follows our Commit Message Guidelines
  • Tests for the changes have been added (for bug fixes / features)
  • Docs have been added / updated (for bug fixes / features)

PR Type

What kind of change does this PR introduce?

  • Bugfix
  • Feature
  • Code style update (formatting, local variables)
  • Refactoring (no functional changes, no api changes)
  • Build related changes
  • CI related changes
  • Documentation content changes
  • Other... Please describe:

What is the current behavior?

Issue Number: N/A

What is the new behavior?

Does this PR introduce a breaking change?

  • Yes
  • No

Other information

Summary by CodeRabbit

  • New Features

    • Enhanced tree select component with improved handling of selected values.
    • Introduced a new watchValue function for real-time updates in multi-select scenarios.
    • Added :check-strictly prop to influence node selection behavior.
  • Bug Fixes

    • Updated model binding to align with Vue 3 conventions, ensuring better reactivity.
  • Documentation

    • Clarified usage of modelValue and selected parameters in the tree select component.

@kagol kagol added the bug Something isn't working label Nov 22, 2024
@kagol kagol marked this pull request as draft November 22, 2024 04:02
Copy link

coderabbitai bot commented Nov 22, 2024

Walkthrough

The pull request introduces significant updates to the tree select component across multiple files. Key changes include modifications to the getCheckedData and mounted functions to improve how selected values are handled, particularly for multi-select scenarios. A new watchValue function is added to monitor changes in the model value. Additionally, the vue.ts file is updated to synchronize the state with props, and the pc.vue component aligns with Vue 3 conventions by updating the v-model binding and adding new props.

Changes

File Path Change Summary
packages/renderless/src/tree-select/index.ts - Updated getCheckedData to accept a selected parameter.
- Changed mounted to reference state.modelValue.
- Added watchValue function to handle changes in selected nodes.
packages/renderless/src/tree-select/vue.ts - Imported watchValue and updated state to use modelValue.
- Added watchers for state.modelValue and props.modelValue.
packages/vue/src/tree-select/src/pc.vue - Renamed v-model binding from state.value to state.modelValue.
- Added new prop :check-strictly for tiny-tree.

Possibly related PRs

Suggested reviewers

  • zzcr

Poem

🐰 In the trees where bunnies play,
The select now shines bright as day.
With model values clear and true,
We hop along, just me and you.
Watch us change with every click,
Tree select magic, oh so quick! 🌿


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ 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.
    • Generate unit testing code for this file.
    • 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 generate unit testing code for this file.
    • @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 generate unit testing code.
    • @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.

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 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

[e2e-test-warn]
The component to be tested is missing.

The title of the Pull request should look like "fix(vue-renderless): [action-menu, alert] fix xxx bug".

Please make sure you've read our contributing guide

Copy link

@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: 1

🧹 Outside diff range and nitpick comments (5)
packages/renderless/src/tree-select/vue.ts (2)

14-14: Consider adding documentation for modelValue state

The change to use modelValue aligns with Vue 3 conventions. Consider adding a comment to document whether this array holds selected node values or node objects.

-    modelValue: []
+    modelValue: [], // Holds selected node values/objects for v-model binding

35-45: Consider adding null checks and type validation

The watcher implementation correctly handles both multiple and single selection modes. However, consider adding null checks and type validation for more robust error handling.

   watch(
     () => props.modelValue,
     () => {
+      if (props.modelValue === null || props.modelValue === undefined) {
+        state.modelValue = props.multiple ? [] : null;
+        return;
+      }
       if (props.multiple && Array.isArray(props.modelValue)) {
         state.modelValue = [...props.modelValue]
       } else {
+        if (props.multiple) {
+          console.warn('modelValue should be an array when multiple is true');
+          state.modelValue = [];
+          return;
+        }
         state.modelValue = props.modelValue
       }
     },
     { immediate: true, deep: true }
   )
packages/vue/src/tree-select/src/pc.vue (2)

5-5: LGTM! Consider adding migration docs.

The change from state.value to state.modelValue correctly aligns with Vue 3's v-model convention.

Consider adding a note in the migration guide about this change if upgrading from Vue 2, as it might affect existing implementations.


Line range hint 1-67: Add test coverage for deleteTag functionality.

The PR objectives indicate that tests haven't been added. Please add test cases to cover:

  1. deleteTag functionality with check-strictly enabled/disabled
  2. Multiple selection scenarios
  3. Edge cases (empty selection, all tags deleted)

Would you like me to help generate test cases for these scenarios?

packages/renderless/src/tree-select/index.ts (1)

Line range hint 118-130: Refactor function to improve type consistency and clarity

The current implementation has several issues:

  1. Inconsistent return types between array and non-array cases
  2. Implicit dependency on props.modelValue in non-array case
  3. Function name doesn't accurately reflect its purpose

Consider this improved implementation:

 export const getCheckedData =
   ({ props, state }) =>
   (selected) => {
-    const checkedKey = []
-
-    if (!Array.isArray(selected)) {
-      return props.modelValue ? [props.modelValue] : [selected[props.valueField]]
-    } else {
-      selected.length > 0 &&
-        selected.forEach((item) => {
-          checkedKey.push(item[props.valueField])
-        })
-
-      return checkedKey
-    }
+    if (!selected) {
+      return []
+    }
+    
+    const items = Array.isArray(selected) ? selected : [selected]
+    return items.map(item => item[props.valueField])
   }

Also consider renaming the function to getSelectedValues to better reflect its purpose.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 394447d and 5f97d7c.

📒 Files selected for processing (3)
  • packages/renderless/src/tree-select/index.ts (3 hunks)
  • packages/renderless/src/tree-select/vue.ts (4 hunks)
  • packages/vue/src/tree-select/src/pc.vue (2 hunks)
🔇 Additional comments (5)
packages/renderless/src/tree-select/vue.ts (3)

25-26: LGTM! API extension follows consistent pattern

The watchValue method is properly integrated into the API object with the required dependencies.


1-1: Verify the watchValue implementation in index.ts

The import statement correctly includes the new watchValue function.

Let's verify the implementation of watchValue in the index file:

✅ Verification successful

watchValue implementation is correctly handling tag deletion in multiple selection mode

The implementation in index.ts shows that watchValue properly handles the tag deletion scenario by:

  1. Correctly processing the new value after tag deletion through array handling
  2. Properly updating both the base select component and tree component states
  3. Maintaining synchronization between the tree's checked state and select's selected items

The function implementation aligns with the PR's objective to fix the "deleteTag not working" issue by ensuring proper state updates when tags are removed in multiple selection mode.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check the implementation of watchValue in index.ts
ast-grep --pattern 'export const watchValue = $_' packages/renderless/src/tree-select/index.ts

Length of output: 2047


47-47: Verify deleteTag interaction with modelValue watcher

The watcher chain looks correct for handling tag deletion: when a tag is deleted, it should update state.modelValue, which triggers this watcher to call api.watchValue, ultimately syncing the tree's checked state.

Let's verify the deleteTag interaction:

✅ Verification successful

Tag deletion is handled through base-select component

After examining the implementation, I notice that tag deletion is not directly handled in the tree-select component. Instead, it relies on the base-select component (baseSelectRef) which emits updates to modelValue. When modelValue changes, the watcher correctly triggers watchValue which:

  1. Maps the new values to tree nodes
  2. Updates the selected data in base-select
  3. Syncs the tree's checked state via setCheckedKeys

The interaction chain is complete and working as expected.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check the deleteTag implementation and its interaction with modelValue
# Look for deleteTag handler and its usage of modelValue
rg -A 5 "deleteTag|handleTagClose" packages/renderless/src/tree-select/

Length of output: 73


Script:

#!/bin/bash
# Let's try a broader search for tag-related operations in the tree-select component
ast-grep --pattern 'function $_($$_) {
  $$$
  modelValue
  $$$
}'  packages/renderless/src/tree-select/

# Also search for any tag-related methods
rg -A 10 "tag" packages/renderless/src/tree-select/

Length of output: 157


Script:

#!/bin/bash
# Let's examine the entire tree-select API and state implementation
cat packages/renderless/src/tree-select/vue.ts

# Also check for any tag-related functionality in the interface definitions
cat packages/renderless/src/tree-select/interface.ts

Length of output: 1658


Script:

#!/bin/bash
# Let's examine the index file which contains the actual implementation of watchValue and other methods
cat packages/renderless/src/tree-select/index.ts

Length of output: 4925

packages/vue/src/tree-select/src/pc.vue (1)

14-14: Verify the impact of check-strictly on deleteTag functionality.

The addition of :check-strictly binding could affect the tree's selection behavior. Since this PR aims to fix the deleteTag functionality:

  1. Please verify that this change resolves the deleteTag issue
  2. Document the expected behavior with check-strictly enabled/disabled

Let's verify the usage and documentation of this prop:

✅ Verification successful

Based on the code analysis, I can now generate the final response:

The check-strictly prop addition is valid and improves tree-select's functionality

The check-strictly prop is a standard tree component feature that controls parent-child node selection behavior:

  • When true: Parent and child node selections are independent
  • When false (default): Selecting a parent auto-selects children and vice versa

This addition:

  1. Properly passes through the checkStrictly configuration from treeOp props to the underlying tree component
  2. Aligns with the tree component's documented behavior
  3. Gives users control over whether tag deletion should affect parent/child nodes
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for existing documentation or tests related to check-strictly
rg -l "check.*strictly" docs/

# Search for related test cases
rg -l "deleteTag.*check.*strictly" tests/

# Look for similar implementations in the codebase
ast-grep --pattern 'check-strictly="$_"'

Length of output: 211


Script:

#!/bin/bash
# Let's first find the correct documentation and test directories
fd -t d -i "docs\|documentation"
fd -t d -i "test"

# Search for check-strictly usage in the codebase
rg -i "checkStrictly|check-strictly" -A 3

# Look for tree-select component documentation
fd -t f "tree-select" 

# Search for deleteTag implementation
rg "deleteTag" -A 5

Length of output: 89383


Script:

#!/bin/bash
# Let's look at the tree-select implementation and documentation
rg -A 5 "checkStrictly" packages/vue/src/tree-select/
rg -A 5 "deleteTag" packages/vue/src/tree-select/

# Check tree component's checkStrictly behavior
rg -A 5 "checkStrictly" packages/vue/src/tree/src/

# Look for tree-select tests
fd -t f "tree-select.*test" 

Length of output: 1992

packages/renderless/src/tree-select/index.ts (1)

Line range hint 136-172: Add null checks and extract duplicate logic

The function has several potential issues:

  1. Missing null check before accessing data properties in single selection mode
  2. Duplicate logic with watchValue for mapping selected nodes
  3. No error handling for missing refs

Add null checks for single selection mode:

     } else {
       const data = api.getPluginOption(state.modelValue)[0]
+      if (!data) {
+        return
+      }
       vm.$refs.baseSelectRef.updateSelectedData({

Extract the duplicate node mapping logic into a shared function:

const mapNodeToSelected = (node, props) => ({
  ...node,
  currentLabel: node[props.textField],
  value: node[props.valueField],
  isTree: true
})

Let's verify the refs usage pattern across the codebase:

✅ Verification successful

Based on the search results, I can see that the codebase consistently performs null checks before accessing $refs properties. Let me verify the specific pattern for tree-select by searching for its usage:


Let me verify the null check pattern in the tree-select component's implementation:


Based on the code analysis, I can now generate the final response:

Add null checks and error handling for refs access

The code needs to add null checks before accessing $refs properties to prevent potential runtime errors:

    } else {
      const data = api.getPluginOption(state.modelValue)[0]
+     if (!data || !vm.$refs.baseSelectRef) {
+       return
+     }
      vm.$refs.baseSelectRef.updateSelectedData({
        ...data,
        currentLabel: data[props.textField],
        value: data[props.valueField],

The same pattern should be applied to all $refs accesses in the component:

  • Add null check for vm.$refs.baseSelectRef before calling its methods
  • Add null check for vm.$refs.treeRef before calling its methods
  • Add null check for data objects before accessing their properties

This follows the established pattern seen across the codebase where refs are consistently checked before use.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for error handling patterns around $refs usage
rg -A 2 '\$refs\.' --type ts

Length of output: 103211


Script:

#!/bin/bash
# Search for tree-select refs usage patterns
rg -A 3 '\$refs\.' --type ts packages/renderless/src/tree-select/

Length of output: 2178


Script:

#!/bin/bash
# Search for the implementation context around refs usage in tree-select
rg -B 3 -A 3 '\$refs\.baseSelectRef\.updateSelectedData' packages/renderless/src/tree-select/index.ts

Length of output: 1025

Comment on lines +174 to +199
export const watchValue =
({ api, props, vm, state }) =>
(newValue, oldValue) => {
if (props.multiple) {
let initialNodes = []
if (Array.isArray(newValue)) {
newValue.forEach((value) => {
const option = api.getPluginOption(value)
initialNodes = initialNodes.concat(option)
})
}

const selected = initialNodes.map((node) => {
return {
...node,
currentLabel: node[props.textField],
value: node[props.valueField],
isTree: true
}
})

vm.$refs.baseSelectRef.updateSelectedData(selected)

vm.$refs.treeRef.setCheckedKeys(newValue)
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Handle single selection mode and value removal

The current implementation has several limitations:

  1. Only handles multiple selection mode
  2. Doesn't properly handle value removal (the original bug)
  3. Potential race condition between updateSelectedData and setCheckedKeys

Consider this enhanced implementation:

 export const watchValue =
   ({ api, props, vm, state }) =>
   (newValue, oldValue) => {
+    // Handle value removal
+    if (!newValue || (Array.isArray(newValue) && newValue.length === 0)) {
+      vm.$refs.baseSelectRef.updateSelectedData([])
+      vm.$refs.treeRef.setCheckedKeys([])
+      return
+    }
+
     if (props.multiple) {
       let initialNodes = []
       if (Array.isArray(newValue)) {
         newValue.forEach((value) => {
           const option = api.getPluginOption(value)
           initialNodes = initialNodes.concat(option)
         })
       }

       const selected = initialNodes.map((node) => {
         return {
           ...node,
           currentLabel: node[props.textField],
           value: node[props.valueField],
           isTree: true
         }
       })

       vm.$refs.baseSelectRef.updateSelectedData(selected)
-
       vm.$refs.treeRef.setCheckedKeys(newValue)
+    } else {
+      const data = api.getPluginOption(newValue)[0]
+      if (!data) {
+        vm.$refs.baseSelectRef.updateSelectedData(null)
+        return
+      }
+      vm.$refs.baseSelectRef.updateSelectedData({
+        ...data,
+        currentLabel: data[props.textField],
+        value: data[props.valueField],
+        state: {
+          currentLabel: data[props.textField]
+        }
+      })
     }
   }

This implementation:

  • Properly handles value removal (fixes the deleteTag bug)
  • Adds support for single selection mode
  • Adds null checks
📝 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
export const watchValue =
({ api, props, vm, state }) =>
(newValue, oldValue) => {
if (props.multiple) {
let initialNodes = []
if (Array.isArray(newValue)) {
newValue.forEach((value) => {
const option = api.getPluginOption(value)
initialNodes = initialNodes.concat(option)
})
}
const selected = initialNodes.map((node) => {
return {
...node,
currentLabel: node[props.textField],
value: node[props.valueField],
isTree: true
}
})
vm.$refs.baseSelectRef.updateSelectedData(selected)
vm.$refs.treeRef.setCheckedKeys(newValue)
}
}
export const watchValue =
({ api, props, vm, state }) =>
(newValue, oldValue) => {
// Handle value removal
if (!newValue || (Array.isArray(newValue) && newValue.length === 0)) {
vm.$refs.baseSelectRef.updateSelectedData([])
vm.$refs.treeRef.setCheckedKeys([])
return
}
if (props.multiple) {
let initialNodes = []
if (Array.isArray(newValue)) {
newValue.forEach((value) => {
const option = api.getPluginOption(value)
initialNodes = initialNodes.concat(option)
})
}
const selected = initialNodes.map((node) => {
return {
...node,
currentLabel: node[props.textField],
value: node[props.valueField],
isTree: true
}
})
vm.$refs.baseSelectRef.updateSelectedData(selected)
} else {
const data = api.getPluginOption(newValue)[0]
if (!data) {
vm.$refs.baseSelectRef.updateSelectedData(null)
return
}
vm.$refs.baseSelectRef.updateSelectedData({
...data,
currentLabel: data[props.textField],
value: data[props.valueField],
state: {
currentLabel: data[props.textField]
}
})
}
}

@kagol kagol changed the title fix(tree-select): fix deleteTag not working fix(tree-select): [tree-select] fix deleteTag not working Nov 22, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant