Skip to content

Instantly share code, notes, and snippets.

@scf4
Last active April 26, 2024 00:58
Show Gist options
  • Star 15 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save scf4/012e9f615f6b43a1712a083b162afd94 to your computer and use it in GitHub Desktop.
Save scf4/012e9f615f6b43a1712a083b162afd94 to your computer and use it in GitHub Desktop.
react native selectively highlight input text (mentions)
import React from 'react';
import { View, Text, TextInput, StyleSheet } from 'react-native';
const styles = StyleSheet.create({
wrapper: {
width: '90%',
height: 24,
position: 'relative',
alignSelf: 'center',
},
inputWrapper: {
position: 'absolute',
top: 0,
height: 24,
width: '100%',
borderWidth: 1,
borderColor: 'gray',
},
input: {
height: 24,
fontSize: 18,
width: '100%',
},
text: {
height: 24,
fontSize: 18,
position: 'absolute',
top: 0,
color: 'transparent',
},
mention: {
backgroundColor: 'rgba(0, 150, 255, .5)',
}
});
export default class App extends React.Component {
state = {
inputText: '',
formattedText: '',
}
handleChangeText = (inputText) => {
const words = inputText.split(' ');
const formattedText = [];
words.forEach(word => {
if (!word.startsWith('@')) return formattedText.push(word, ' ');
const mention = (
<Text key={word} style={styles.mention}>
{word}
</Text>
);
formattedText.push(mention, ' ');
});
this.setState({ inputText, formattedText });
}
render() {
return (
<View style={{ marginTop: 48 }}>
<View style={styles.wrapper}>
<Text style={styles.text}>
{this.state.formattedText}
</Text>
<View style={styles.inputWrapper}>
<TextInput
style={styles.input}
value={this.state.inputText}
onChangeText={this.handleChangeText}
/>
</View>
</View>
</View>
);
}
}
@nitishxyz
Copy link

You have to use this for multiline code:

  handleChangeText = inputText => {
    const retLines = inputText.split("\n");
    const formattedText = [];
    retLines.forEach(retLine => {
      const words = retLine.split(" ");
      var format = /[ !#@$%^&*()_+\-=\[\]{};':"\\|,.<>\/?\n]/;
      words.forEach(word => {
        if (
          (word.startsWith("@") && !format.test(word.substr(1))) ||
          (word.startsWith("#") && !format.test(word.substr(1)))
        ) {
          const mention = (
            <Text key={word} style={styles.mention}>
              {word}
            </Text>
          );
          formattedText.push(mention, " ");
        } else {
          formattedText.push(word, " ");
        }
      });
      formattedText.push("\n");
    });
    this.setState({ captionText: inputText, caption: formattedText });
  };

@comphonia
Copy link

comphonia commented Aug 21, 2020

Lovely! here's an update I made that worked best for me setup.

What I wanted: A TextInput that highlights mentions and hashtags .

Note: If want your "Display" and "Input Area" as separate entities, the 2 snippets above will work, but if you want some sort of a rich text editor that highlights as you type the guide below might help.

  1. Wrap your TextInput around a Text component
  2. Remove the value props from the TextInput
  3. Add an onChangeText prop that calls your handleChangeText .
  const handleChangeText = (inputText) => {
    const retLines = inputText.split("\n");
    const formattedText = [];
    retLines.forEach((retLine) => {
      const words = retLine.split(" ");
      const contentLength = words.length;
      var format = /[ !#@$%^&*()_+\-=\[\]{};':"\\|,.<>\/?\n]/;
      words.forEach((word,index) => {
        if (
          (word.startsWith("@") && !format.test(word.substr(1))) ||
          (word.startsWith("#") && !format.test(word.substr(1)))
        ) {
          const mention = (
            <Text key={index} style={styles.mention}>
              {word}
            </Text>
          );
          if (index !== contentLength - 1) formattedText.push(mention, " ");
          else formattedText.push(mention);
        } else {
          if (index !== contentLength - 1) return formattedText.push(word, " ");
          else return formattedText.push(word);
        }
      });
    });

    setContent(inputText);// still update your raw text, this will probably go to your api
    setFormattedContent(formattedText);
  };
//--------render------------
 <TextInput
  style={styles.input}
  onChangeText={handleChangeText}
 >
<Text>{formattedContent}</Text>
</TextInput>

Sample:

@scf4
Copy link
Author

scf4 commented Aug 21, 2020

@comphonia really cool. I’m looking for something like this that’s production ready. I wonder if there are existing libraries or perhaps we could make one?

Lovely! here's an update I made that worked best for me setup.

What I wanted: A TextInput that highlights mentions and hashtags .

Note: If want your "Display" and "Input Area" as separate entities, the original code will work, but if you want some sort of a rich text editor that highlights as you type the guide below might help.

  1. Wrap your TextInput around a Text component
  2. Remove the value props from the TextInput
  3. Add an onChangeText prop that calls your handleChangeText .
  const handleChangeText = (inputText) => {
    const words = inputText.split(" ");
    const contentLength = words.length;
    const formattedText = [];
    words.forEach((word, index) => {
      if (!word.startsWith("@") && !word.startsWith("#")) {
        if (index !== contentLength - 1) return formattedText.push(word, " ");
        else return formattedText.push(word);
      }
      const mention = (
        <Text key={index} style={styles.mention}>
          {word}
        </Text>
      );
      if (index !== contentLength - 1) formattedText.push(mention, " ");
      else formattedText.push(mention);
    });
    setContent(inputText); // still update your raw text, this will probably go to your api
    setFormattedContent(formattedText); 
  };
//--------render------------
 <TextInput
  style={styles.input}
  onChangeText={handleChangeText}
 >
<Text>{formattedContent}</Text>
</TextInput>

Sample:

@comphonia
Copy link

@scf4 Not really, I think most build their own stuff with react native. I saw this(https://github.com/harshq/react-native-mentions), but it's not so modular.

We can definitely make one!

Your snippet was very helpful for syntax highlighting.

Here's the finished result

@vinhtran6921
Copy link

@scf4 Not really, I think most build their own stuff with react native. I saw this(https://github.com/harshq/react-native-mentions), but it's not so modular.

We can definitely make one!

Your snippet was very helpful for syntax highlighting.

Here's the finished result

Can you show me your code this feature? I had a issue when code syntax highlighting. When user comeback hashtag or mention before, I can't detect it to show list recommend. And it only defined hashtag when had space before (#hashtag#hashtag2 can't defined)

@nanhlua96
Copy link

Lovely! here's an update I made that worked best for me setup.

What I wanted: A TextInput that highlights mentions and hashtags .

Note: If want your "Display" and "Input Area" as separate entities, the 2 snippets above will work, but if you want some sort of a rich text editor that highlights as you type the guide below might help.

  1. Wrap your TextInput around a Text component
  2. Remove the value props from the TextInput
  3. Add an onChangeText prop that calls your handleChangeText .
  const handleChangeText = (inputText) => {
    const retLines = inputText.split("\n");
    const formattedText = [];
    retLines.forEach((retLine) => {
      const words = retLine.split(" ");
      const contentLength = words.length;
      var format = /[ !#@$%^&*()_+\-=\[\]{};':"\\|,.<>\/?\n]/;
      words.forEach((word,index) => {
        if (
          (word.startsWith("@") && !format.test(word.substr(1))) ||
          (word.startsWith("#") && !format.test(word.substr(1)))
        ) {
          const mention = (
            <Text key={index} style={styles.mention}>
              {word}
            </Text>
          );
          if (index !== contentLength - 1) formattedText.push(mention, " ");
          else formattedText.push(mention);
        } else {
          if (index !== contentLength - 1) return formattedText.push(word, " ");
          else return formattedText.push(word);
        }
      });
    });

    setContent(inputText);// still update your raw text, this will probably go to your api
    setFormattedContent(formattedText);
  };
//--------render------------
 <TextInput
  style={styles.input}
  onChangeText={handleChangeText}
 >
<Text>{formattedContent}</Text>
</TextInput>

Sample:

seem if you not set value for text input, selection on IOS not work. Do you have any idea on IOS

@ElicaInc
Copy link

super cool! thanks

@kindacoder
Copy link

kindacoder commented Jan 15, 2022

Hi @comphonia, Can you please show me the code for https://gist.github.com/scf4/012e9f615f6b43a1712a083b162afd94#gistcomment-3426070 ? Was facing some issues

@samstorino
Copy link

samstorino commented Jan 22, 2022

This works great but I'm seeing some unexpected behavior on my end.

I have "mention" color set to red, and all other text set to green. You'll notice in the GIF that after I move the cursor back to the highlighted portion of the code, then press space and start typing again, each letter from then on is very briefly colored red then flips to green. It's very subtle, and only appears to happen if I move the cursor back to a highlighted word and start typing from there.

I'm digging into it to see if I can figure out what's going on, but figured I'd comment here in case anyone has any ideas.

Simulator Screen Recording - iPhone 12 Pro Max - 2022-01-21 at 16 31 15

This is a fresh react native project created via expo init. The entirety of the project is pasted below:

package.json

{
  "name": "reactnativeplayground",
  "version": "1.0.0",
  "main": "index.js",
  "scripts": {
    "start": "expo start --dev-client",
    "android": "expo run:android",
    "ios": "expo run:ios",
    "web": "expo start --web"
  },
  "dependencies": {
    "expo": "~44.0.2",
    "expo-splash-screen": "~0.14.1",
    "expo-status-bar": "~1.2.0",
    "react": "17.0.1",
    "react-dom": "17.0.1",
    "react-native": "0.64.3",
    "react-native-web": "0.17.1"
  },
  "devDependencies": {
    "@babel/core": "^7.12.9"
  },
  "private": true
}

App.js

import { StatusBar } from 'expo-status-bar';
import React from 'react';
import { StyleSheet, Text, View, TextInput } from 'react-native';

export default function App() {
  const [testContent, setTestContent] = React.useState('');
  const [testFormattedContent, setTestFormattedContent] = React.useState('');

  const handleChangeText = (inputText) => {
    const retLines = inputText.split("\n");
    const formattedText = [];
    retLines.forEach((retLine) => {
      const words = retLine.split(" ");
      const contentLength = words.length;
      var format = /[ !#@$%^&*()_+\-=\[\]{};':"\\|,.<>\/?\n]/;
      words.forEach((word,index) => {
        if (
          (word.startsWith("@") && !format.test(word.substr(1))) ||
          (word.startsWith("#") && !format.test(word.substr(1)))
        ) {
          const mention = (
            <Text key={index} style={{ color: 'red' }}>
              {word}
            </Text>
          );
          if (index !== contentLength - 1) formattedText.push(mention, " ");
          else formattedText.push(mention);
        } else {
          if (index !== contentLength - 1) return formattedText.push(word, " ");
          else return formattedText.push(word);
        }
      });
    });

    setTestContent(inputText);// still update your raw text, this will probably go to your api
    setTestFormattedContent(formattedText);
  };

  return (
    <View style={styles.container}>
      <TextInput
        style={{ flex: 1, margin: 50, borderColor: 'black', borderWidth: 1, color: 'green' }}
        onChangeText={handleChangeText}
      >
        <Text style={{ color: 'green' }}>{testFormattedContent}</Text>
      </TextInput>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
    // alignItems: 'center',
    // justifyContent: 'center',
  },
});

@Darmolar
Copy link

Darmolar commented Feb 1, 2024

<Text key={index} style={{ color: 'red' }}>
{word}

Thank you soooooooo much for this it worked perfectly

@Lu1815
Copy link

Lu1815 commented Apr 26, 2024

@scf4 Not really, I think most build their own stuff with react native. I saw this(https://github.com/harshq/react-native-mentions), but it's not so modular.

We can definitely make one!

Your snippet was very helpful for syntax highlighting.

Here's the finished result 68747470733a2f2f6d656469612e67697068792e636f6d2f6d656469612f4a6d4f48357054724f3541364f556c4536622f67697068792e676966

@comphonia hey man, do you have some source code for what you showed in the gif?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment