Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion app/containers/MessageComposer/MessageComposer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ export const MessageComposer = ({
return;
}

const textFromInput = composerInputComponentRef.current.getTextAndClear();
const textFromInput = composerInputComponentRef.current.getTextAndClear().trim();

if (action === 'edit') {
return editRequest?.({ id: selectedMessages[0], msg: textFromInput, rid });
Expand Down
47 changes: 41 additions & 6 deletions app/containers/MessageComposer/components/ComposerInput.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { forwardRef, memo, useCallback, useEffect, useImperativeHandle } from 'react';
import React, { forwardRef, memo, useCallback, useEffect, useImperativeHandle, useState } from 'react';
import { TextInput, StyleSheet, type TextInputProps, InteractionManager } from 'react-native';
import { useDebouncedCallback } from 'use-debounce';
import { useDispatch } from 'react-redux';
Expand Down Expand Up @@ -47,6 +47,7 @@ const defaultSelection: IInputSelection = { start: 0, end: 0 };

export const ComposerInput = memo(
forwardRef<IComposerInput, IComposerInputProps>(({ inputRef }, ref) => {
const [inputValue, setInputValue] = useState('');
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 | 🟠 Major

🧩 Analysis chain

Verify the controlled input behavior.

Converting the TextInput from uncontrolled to controlled by introducing the inputValue state is a significant change. Ensure that this doesn't introduce issues with cursor positioning, text selection, or input performance, especially for longer messages.

Run the following script to check if there are any related issues or tests that might be affected:


🏁 Script executed:

#!/bin/bash
# Description: Search for tests or usage patterns related to ComposerInput text handling

# Search for test files related to ComposerInput
fd -e test.tsx -e test.ts -e spec.tsx -e spec.ts | xargs rg -l "ComposerInput" -C 2

# Search for places where inputRef is used with setNativeProps
rg -n "inputRef.*setNativeProps" -A 3 -B 3

# Search for potential issues with controlled inputs
rg -n "value=\{.*\}" app/containers/MessageComposer -A 2 -B 2

Length of output: 4333


is NOT appropriate here as we need repository files


---

🏁 Script executed:

```shell
#!/bin/bash
# Get the full context of setInput function and surrounding code
sed -n '171,200p' app/containers/MessageComposer/components/ComposerInput.tsx

# Find all setInputValue calls in the file
rg -n "setInputValue" app/containers/MessageComposer/components/ComposerInput.tsx -B 2 -A 2

# Find all setNativeProps calls in the file
rg -n "setNativeProps" app/containers/MessageComposer/components/ComposerInput.tsx -B 3 -A 3

Length of output: 302


🏁 Script executed:

#!/bin/bash
# Find the actual file
fd "ComposerInput.tsx" -type f

# Get content using find + cat
fd "ComposerInput.tsx" -type f -exec cat {} \;

Length of output: 341


🏁 Script executed:

#!/bin/bash
# Find the file with correct fd syntax
fd "ComposerInput.tsx"

# List files to see structure
find . -name "ComposerInput.tsx" 2>/dev/null

# Try to get lines around setNativeProps and value binding
rg -n "setNativeProps|value=\{inputValue" --no-heading -C 10

Length of output: 8444


Remove setNativeProps call when using controlled input with state.

The conversion to controlled input (via inputValue state on line 403) conflicts with the direct native manipulation via setNativeProps on line 182. This mixed pattern creates inconsistency between React state and native layer, causing the exact issues mentioned: cursor positioning, text selection, and performance problems.

In a controlled input, React state is the single source of truth. Remove the setNativeProps call on line 182 and rely solely on the state-driven value binding.

🤖 Prompt for AI Agents
In app/containers/MessageComposer/components/ComposerInput.tsx around line 50
(inputValue state) and line 182 (setNativeProps call) — you have a controlled
input via inputValue but are also calling setNativeProps directly; remove the
setNativeProps call at line 182 so React state is the single source of truth.
Ensure the TextInput's value prop is bound to inputValue and onChangeText/update
handlers update setInputValue; remove any direct native ref manipulation related
to text content or cursor positioning, and if selection must be controlled
manage selection via state and props rather than setNativeProps.

const { colors, theme } = useTheme();
const { rid, tmid, sharing, action, selectedMessages, setQuotesAndText, room } = useRoomContext();
const focused = useFocused();
Expand Down Expand Up @@ -168,14 +169,17 @@ export const ComposerInput = memo(
}));

const setInput: TSetInput = (text, selection, forceUpdateDraftMessage) => {
const message = text.trim();
// const message = forceUpdateDraftMessage ? text : text.trim();
const message = text;
textRef.current = message;

setInputValue(message);

if (forceUpdateDraftMessage) {
saveMessageDraft('');
}

inputRef.current?.setNativeProps?.({ text });
inputRef.current?.setNativeProps?.({ text: message });

if (selection) {
// setSelection won't trigger onSelectionChange, so we need it to be ran after new text is set
Expand All @@ -195,10 +199,40 @@ export const ComposerInput = memo(
}, 300);
};

const handleAutoBullet = useCallback((text: string, prevText: string): string => {
if (text.endsWith('\n')) {
const lines = text.split('\n');

// check for deletion
if (text.length < prevText.length) {
return text;
}
const prevLine = lines[lines.length - 2];
console.log('prevline', prevLine);

const regex = /^((?:\d+\.|-\s)).*/;

if (prevLine && regex.test(prevLine)) {
if (prevLine.startsWith('- ')) {
const newText = `${text}- `;
console.log('new text', newText);
return newText;
}
const prevNumber = parseInt(prevLine.split('.')[0], 10);
const nextNumber = prevNumber + 1;
const newText = `${text}${nextNumber}. `;
console.log('new text', newText);
return newText;
}
}
return text;
}, []);
Comment on lines +202 to +229
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 | 🟠 Major

Remove debug console.log statements and fix regex pattern.

The function has several issues:

  1. Debug logs (lines 211, 218, 224): Remove console.log statements before merging to production.
  2. Regex inconsistency (line 213): The pattern /^((?:\d+\.|-\s)).*/ captures -\s (hyphen + space) but line 216 checks for '- ' (hyphen + space). The regex should be /^(\d+\.|-\s)/ or /^(\d+\.|- )/ for consistency.
  3. Missing exit logic: The feature doesn't handle exiting the bullet list when a user presses enter on an empty bullet line (e.g., "- \n" should not add another "- "). Consider adding logic to detect empty bullet lines and stop auto-insertion.

Apply this diff to fix the regex and remove debug logs:

 const handleAutoBullet = useCallback((text: string, prevText: string): string => {
   if (text.endsWith('\n')) {
     const lines = text.split('\n');
 
     // check for deletion
     if (text.length < prevText.length) {
       return text;
     }
     const prevLine = lines[lines.length - 2];
-    console.log('prevline', prevLine);
 
-    const regex = /^((?:\d+\.|-\s)).*/;
+    const regex = /^(\d+\.|-)(\s)/;
 
     if (prevLine && regex.test(prevLine)) {
       if (prevLine.startsWith('- ')) {
+        // Don't add bullet if previous line is empty bullet
+        if (prevLine.trim() === '-') {
+          return text.slice(0, -1); // Remove the newline, exit bullet mode
+        }
         const newText = `${text}- `;
-        console.log('new text', newText);
         return newText;
       }
       const prevNumber = parseInt(prevLine.split('.')[0], 10);
       const nextNumber = prevNumber + 1;
       const newText = `${text}${nextNumber}. `;
-      console.log('new text', newText);
       return newText;
     }
   }
   return text;
 }, []);

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In app/containers/MessageComposer/components/ComposerInput.tsx around lines 202
to 229, remove all debug console.log statements and fix the regex and exit
logic: replace the regex with a consistent pattern like /^(\d+\.|- )/ (or
/^(\d+\.|- )/), remove console.log calls at lines ~211, ~218, ~224, and add
handling to detect empty bullet lines (e.g., if prevLine matches /^- $/ or
/^\d+\. $/ or prevLine.trim() === '-' then do not auto-insert another bullet and
return text unchanged) so pressing Enter on an empty bullet line exits the list;
keep the existing numeric increment logic otherwise.


const onChangeText: TextInputProps['onChangeText'] = text => {
textRef.current = text;
debouncedOnChangeText(text);
setInput(text);
const newText = handleAutoBullet(text, textRef.current);
textRef.current = newText;
debouncedOnChangeText(newText);
setInput(newText);
};

const onSelectionChange: TextInputProps['onSelectionChange'] = e => {
Expand Down Expand Up @@ -366,6 +400,7 @@ export const ComposerInput = memo(

return (
<TextInput
value={inputValue}
style={[styles.textInput, { color: colors.fontDefault }]}
placeholder={placeholder}
placeholderTextColor={colors.fontAnnotation}
Expand Down
5 changes: 5 additions & 0 deletions app/containers/MessageComposer/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ export interface IInputSelection {
end: number;
}

/**
* @param text - text value to set in the input field
* @param selection - (Optional) text selection range, start and end. if not passed cursor will be set where it is (end)
* @param forceUpdateDraftMessage - (Optional) forces immediate saving of message draft.
*/
export type TSetInput = (text: string, selection?: IInputSelection, forceUpdateDraftMessage?: boolean) => void;

export type TMicOrSend = 'mic' | 'send';
Expand Down