• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

jcubic / 10xDevs / 19946653658

04 Dec 2025 10:56PM UTC coverage: 62.673%. Remained the same
19946653658

push

github

jcubic
10xDev Project

303 of 501 branches covered (60.48%)

Branch coverage included in aggregate %.

555 of 864 new or added lines in 49 files covered. (64.24%)

4 existing lines in 1 file now uncovered.

555 of 868 relevant lines covered (63.94%)

214.35 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

79.17
/src/hooks/useNodeSelection.ts
1
/**
2
 * @module hooks/useNodeSelection
3
 * @description Custom React hook for managing note tree selection and state.
4
 * Provides centralized state management for note selection, content updates, dirty flags, and save status.
5
 *
6
 * @dependencies
7
 * - @/types/tree: NoteTreeNode type definition
8
 * - @/types/notes: SaveStatus type definition
9
 *
10
 * @remarks
11
 * - Used in NotesLayoutWrapper to manage note list and selection state
12
 * - Ensures only one note is selected at a time
13
 * - Tracks dirty flags for unsaved changes
14
 * - Optimized with useCallback to prevent unnecessary re-renders
15
 * - Immutable state updates for predictable behavior
16
 *
17
 * @example
18
 * ```tsx
19
 * function NotesManager() {
20
 *   const {
21
 *     notes,
22
 *     selectedNoteId,
23
 *     saveStatus,
24
 *     updateSelection,
25
 *     updateNoteContent,
26
 *     updateDirtyFlag
27
 *   } = useNodeSelection(initialNotes, firstNoteId);
28
 *
29
 *   return (
30
 *     <div>
31
 *       <NotesList notes={notes} onSelect={updateSelection} />
32
 *       <NoteEditor
33
 *         noteId={selectedNoteId}
34
 *         onChange={(content) => updateNoteContent(selectedNoteId, content)}
35
 *       />
36
 *     </div>
37
 *   );
38
 * }
39
 * ```
40
 */
41

42
import { useState, useCallback } from 'react';
43
import type { NoteTreeNode } from '@/types/tree';
44
import type { SaveStatus } from '@/types/notes';
45

46
/**
47
 * Custom hook for managing note selection and state in the note tree.
48
 * Provides state and functions for note selection, content updates, and dirty tracking.
49
 *
50
 * @param {NoteTreeNode[]} [initialNotes=[]] - Initial array of note tree nodes
51
 * @param {number | null} [initialSelectedId=null] - Initially selected note ID
52
 *
53
 * @returns {{
54
 *   notes: NoteTreeNode[],
55
 *   selectedNoteId: number | null,
56
 *   saveStatus: SaveStatus,
57
 *   setNotes: (notes: NoteTreeNode[]) => void,
58
 *   setSaveStatus: (status: SaveStatus) => void,
59
 *   updateSelection: (noteId: number | null) => void,
60
 *   updateDirtyFlag: (noteId: number, dirty: boolean) => void,
61
 *   updateNoteContent: (noteId: number, content: string) => void,
62
 *   updateNoteName: (noteId: number, name: string) => void
63
 * }} Hook state and updater functions
64
 *
65
 * @example
66
 * ```tsx
67
 * const {
68
 *   notes,
69
 *   selectedNoteId,
70
 *   updateSelection,
71
 *   updateNoteContent
72
 * } = useNodeSelection(initialNotes, 1);
73
 *
74
 * // Select a different note
75
 * updateSelection(2);
76
 *
77
 * // Update note content (marks as dirty)
78
 * updateNoteContent(2, '# New Content');
79
 * ```
80
 *
81
 * @remarks
82
 * - updateSelection ensures only one note is selected at a time
83
 * - updateNoteContent automatically sets dirty flag to true
84
 * - All updater functions use useCallback for performance
85
 * - State updates are immutable (creates new arrays/objects)
86
 * - saveStatus is independent of note state (managed separately)
87
 */
88
export function useNodeSelection(
89
  initialNotes: NoteTreeNode[] = [],
×
90
  initialSelectedId: number | null = null
×
91
) {
92
  const [notes, setNotes] = useState<NoteTreeNode[]>(initialNotes);
1,048✔
93
  const [selectedNoteId, setSelectedNoteId] = useState<number | null>(initialSelectedId);
1,048✔
94
  const [saveStatus, setSaveStatus] = useState<SaveStatus>('idle');
1,048✔
95

96
  const updateSelection = useCallback((newSelectedId: number | null) => {
1,048✔
NEW
97
    setNotes((prevNotes) => {
×
NEW
98
      const newNotes = prevNotes.map((node) => {
×
99
        // If this is the old selected node, deselect it
100
        if (node.selected && node.id !== newSelectedId) {
588✔
101
          return { ...node, selected: false };
6✔
102
        }
103
        // If this is the new selected node, select it
104
        if (node.id === newSelectedId && !node.selected) {
582✔
105
          return { ...node, selected: true };
58✔
106
        }
107
        // Otherwise return the same node reference
108
        return node;
524✔
109
      });
110

111
      return newNotes;
98✔
112
    });
113

114
    setSelectedNoteId(newSelectedId);
40✔
115
  }, []);
116

117
  const updateDirtyFlag = useCallback((noteId: number, dirty: boolean) => {
1,048✔
NEW
118
    setNotes((prevNotes) =>
×
NEW
119
      prevNotes.map((node) => {
×
120
        if (node.id === noteId) {
16✔
121
          return {
2✔
122
            ...node,
123
            data: { ...node.data!, dirty }
124
          };
125
        }
126
        return node;
14✔
127
      })
128
    );
129
  }, []);
130

131
  const updateNoteContent = useCallback((noteId: number, content: string) => {
1,048✔
NEW
132
    setNotes((prevNotes) =>
×
NEW
133
      prevNotes.map((node) => {
×
134
        if (node.id === noteId) {
6,994✔
135
          return {
646✔
136
            ...node,
137
            data: { ...node.data!, content, dirty: true }
138
          };
139
        }
140
        return node;
6,348✔
141
      })
142
    );
143
  }, []);
144

145
  const updateNoteName = useCallback((noteId: number, name: string) => {
1,048✔
NEW
146
    setNotes((prevNotes) =>
×
NEW
147
      prevNotes.map((node) => {
×
148
        if (node.id === noteId) {
18✔
149
          return { ...node, name };
4✔
150
        }
151
        return node;
14✔
152
      })
153
    );
154
  }, []);
155

156
  return {
1,048✔
157
    notes,
158
    selectedNoteId,
159
    saveStatus,
160
    setNotes,
161
    setSaveStatus,
162
    updateSelection,
163
    updateDirtyFlag,
164
    updateNoteContent,
165
    updateNoteName
166
  };
167
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc