I have a Textarea component that handles Markdown headers:
type TextareaProps = {
initValue: string;
style?: StyleProp<TextStyle>;
onChange?: (value: string) => void;
};
type OnChangeFun = NativeSyntheticEvent<TextInputChangeEventData>;
const Textarea = ({initValue, style, onChange = () => {}}: TextareaProps) => {
const [value, setValue] = useState<string>(initValue);
const changeHandler = ({nativeEvent: {text}}: OnChangeFun) => {
setValue(text);
onChange(text);
};
return (
<TextInput
style={[styles.textarea, style]}
multiline
onChange={changeHandler}>
<Text>
{value.split('n').map((line, index) => {
const style = line.match(/^#/) && styles.header;
return (
<Fragment key={`${index}-${line}`}>
<Text style={style} >{ line }</Text>
{"n"}
</Fragment>
);
})}
</Text>
</TextInput>
);
};
The problem is that when I enter a character the cursor jumps two characters. If it’s the last character in the line it jumps to the next line.
I’ve tried to add controlled selection:
const Textarea = ({initValue, style, onChange = () => {}}: TextareaProps) => {
const [value, setValue] = useState<string>(initValue);
const [selection, setSelection] = useState<Selection>({
start: 0,
end: 0
});
useEffect(() => {
setSelection(({start, end}) => {
if (start === end) {
start += 1;
end = start;
}
return { start, end };
});
}, [value]);
const changeHandler = ({nativeEvent: {text}}: OnChangeFun) => {
setValue(text);
onChange(text);
};
const onSelection = ({ nativeEvent: { selection }}: OnSelectionFun) => {
setSelection(selection);
};
return (
<TextInput
selection={selection}
style={[styles.textarea, style]}
multiline
onSelectionChange={onSelection}
onChange={changeHandler}>
<Text>
{value.split('n').map((line, index) => {
const style = line.match(/^#/) && styles.header;
return (
<Fragment key={`${index}-${line}`}>
<Text style={style} >{ line }</Text>
<Text>{"n"}</Text>
</Fragment>
);
})}
</Text>
</TextInput>
);
};
But this makes the whole content disappear when I type something or click inside the input.
Is there a way to add a new line after each line in the Rich Text editor and have the cursor in the right position?
Unfortunately, I can’t create Snack with the code because this is totally broken in Snack, the output of the Textarea is [object Object]
.
2
Answers
I was looking into all questions at: How can I insert a line break into a component in React Native? and found an answer by Charlie Morton based his idea I came up with this code:
When newline is its own element own item in the array, the text input magically works.
The problem in your original code is caused by adding extra
n
after the last line. Think about it: say you havevalue
with 3 lines. When you render it insideTextInput
, your next value will have 4 lines:If you replace
{"n"}
with conditional render, the problem is solved:P. S. The cursor is moving by 1 extra character because apparently
React Native
tracks cursor position relative to the end of the input value; since you added 1 extra character to the end of the value, cursor remained fixed toN
characters from the end, which caused it to visually jump by 1 extra character to the right with every update.