Socket
Socket
Sign inDemoInstall

react-native-controlled-mentions

Package Overview
Dependencies
578
Maintainers
1
Versions
56
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

    react-native-controlled-mentions

Fully controlled React Native mentions component


Version published
Maintainers
1
Created

Readme

Source

react-native-controlled-mentions npm version

Add to your TextInput ability to highlight mentions, hashtags or other custom patterns. It can:

  • Gracefully render formatted text directly in React Native TextInput component
  • Support for different mention types (@user mentions, #hashtags, etc)
  • Completely typed (written on TypeScript)
  • No need for native libraries

In addition, you can add custom styling for a regex pattern (like URLs).

Contents

Demo

Try it on Expo Snack: https://snack.expo.io/@dabakovich/mentionsapp

Installation

// with npm
npm install --save react-native-controlled-mentions

// with yarn
yarn add react-native-controlled-mentions

Getting Started

For instance, you have next controlled TextInput:

import { TextInput } from 'react-native';

const Mentions = () => {
  const [textValue, setTextValue] = useState('');

  return (
    <TextInput
      value={textValue}
      onChangeText={setTextValue}
    />
  );
};

Now you need few simple steps:

  • Add hook useMentions from the react-native-controlled-mentions
  • Move textValue and setTextValue from TextInput to the just added hook as you see below
  • Use returned textInputProps as new props for the TextInput component now
See code
import { TextInput } from 'react-native';
import { useMentions } from 'react-native-controlled-mentions'

const Mentions = () => {
  const [textValue, setTextValue] = useState('');
  
  const { textInputProps } = useMentions({
    value: textValue,
    onChange: setTextValue,
  });

  return (
    <TextInput {...textInputProps} />
  );
};

Add the triggersConfig property where you can define what trigger types you want to support.

Important. Create the constant once out of functional component body, or memoize it using useMemo to avoid unnecessary re-renders.

See code
import { TextInput } from 'react-native';
import { useMentions, TriggersConfig } from 'react-native-controlled-mentions'

// Create config as static object out of function component
// Or memoize it inside FC using `useMemo`
const triggersConfig: TriggersConfig<'mention'> = {
  mention: {
    // Symbol that will trigger keyword change
    trigger: '@',

    // Style which mention will be highlighted in the `TextInput`
    textStyle: { fontWeight: 'bold', color: 'blue' },
  },
};

const Mentions = () => {
  const [textValue, setTextValue] = useState('');

  const { textInputProps } = useMentions({
    value: textValue,
    onChange: setTextValue,

    // Add the config here
    triggersConfig,
  });

  return (
    <TextInput {...textInputProps} />
  );
};

Define your Suggestions functional component that receives SuggestionsProvidedProps:

See code
import { Pressable, View } from 'react-native';

const suggestions = [
  {
    id: '1',
    name: 'David'
  },
  {
    id: '2',
    name: 'Mary'
  },
  // ...
];

const Suggestions: FC<SuggestionsProvidedProps> = ({
  keyword,
  onSelect
}) => {
  if (keyword == null) {
    return null;
  }

  return (
    <View>
      {suggestions
        .filter(one => one.name.toLocaleLowerCase().includes(keyword.toLocaleLowerCase()))
        .map(one => (
          <Pressable
            key={one.id}
            onPress={() => onSelect(one)}

            style={{padding: 12}}
          >
            <Text>{one.name}</Text>
          </Pressable>
        ))
      }
    </View>
  );
};

export { Suggestions }

useMentions hook returns also triggers value that you can use as provided props for rendering suggestions.

See code
import { TextInput } from 'react-native';
import { useMentions, TriggersConfig } from 'react-native-controlled-mentions'
import { Suggestions } from './suggestions';

const triggersConfig: TriggersConfig<'mention'> = {
  mention: {
    // Symbol that will trigger keyword change
    trigger: '@',

    // Style which mention will be highlighted in the `TextInput`
    textStyle: { fontWeight: 'bold', color: 'blue' },
  },
};

const Mentions = () => {
  const [textValue, setTextValue] = useState('');

  const { textInputProps, triggers } = useMentions({
    value: textValue,
    onChange: setTextValue,

    triggersConfig,
  });

  return (
    <>
      <Suggestions {...triggers.mention} />

      <TextInput {...textInputProps} />
    </>
  );
};

You're done!

The whole example is in the /example folder. You can find also class variant of using mentions, without hooks.

API

Hook useMentions

Receives as parameter UseMentionsConfig<TriggerName> config. Returns an object with two keys:

textInputProps

Props that should be provided to the TextInput components.

Be careful and don't override three required props in the TextInputonChangeText, onSelectionChange, children.

Also, don't provide value to the TextInput directly. Now it's fully controlling by the useMentions hook.

triggers

Object with same keys that has provided triggersConfig object. Values of the triggers has SuggestionsProvidedProps type and can be used in your custom component for rendering suggestions.

Type UseMentionsConfig<TriggerName>

An object with next keys:

value: string

Resulting mention value that should be controlled externally.

onChange: (value: string) => void

Callback that will trigger external value update.

triggersConfig: TriggersConfig<TriggerName>

Config that allows you to define you what trigger types will handle your input (mentions, hashtags, etc.).
It presents an object with TriggerName union type keys (for instance 'mention' | 'hashtag') and TriggerPartType values.

Example
const triggersConfig: TriggersConfig<'mention' | 'hashtag'> = {
  mention: {
    trigger: '@',
  },
  hashtag: {
    trigger: '#',
    allowedSpacesCount: 0,
    isInsertSpaceAfterMention: true,
    textStyle: {
      fontWeight: 'bold',
      color: 'grey',
    },
  },
};
patternsConfig: PatternsConfig

Config that allows to define what another highlights should support your input (like urls, bold, italic text, etc.).
It presents an object with pattern name keys (for instance url, bold) and PatternPartType values.

Example
const patternsConfig: PatternsConfig = {
  url: {
    pattern: /a/gi, // your custom url regex pattern
    textStyle: { color: 'blue' },
  }
}

Type PartType

TriggerPartType | PatternPartType

Type TriggerPartType

Property nameDescriptionTypeRequiredDefault
triggerCharacter that will trigger current mention typestringtrue
allowedSpacesCountHow much spaces are allowed for mention keywordnumberfalse1
isInsertSpaceAfterMentionShould we add a space after selected mentions if the mention is at the end of rowbooleanfalsefalse
textStyleText style for mentions in TextInputStyleProp<TextStyle>false
getPlainStringFunction for generating custom mention text in text input(mention: TriggerData) => stringfalse

Type PatternPartType

Property nameDescriptionTypeRequiredDefault
patternRegExp for parsing a pattern, should include global flagRegExptrue
textStyleText style for pattern in TextInputStyleProp<TextStyle>false

Type SuggestionsProvidedProps

keyword: string | undefined

Keyword that will provide string between trigger character (e.g. '@') and cursor.

If the cursor is not tracking any mention typing the keyword will be undefined.

Examples where @name is just plain text yet, not mention and | is cursor position:

'|abc @name dfg' - keyword is undefined
'abc @| dfg' - keyword is ''
'abc @name| dfg' - keyword is 'name'
'abc @na|me dfg' - keyword is 'na'
'abc @|name dfg' - keyword is against ''
'abc @name |dfg' - keyword is against undefined
onSelect: (suggestion: Suggestion) => void

You should call that callback when user selects any suggestion.

Type Suggestion

id: string

Unique id for each suggestion.

name: string

Name that will be shown in MentionInput when user will select the suggestion.

Type TriggerData

For example, we have that mention value @[David Tabaka](123). Then after parsing that string by mentionRegEx we will get next properties:

original: string

The whole mention value string - @[David Tabaka](123)

trigger: string

The extracted trigger - @

name: string

The extracted name - David Tabaka

id: string

The extracted id - 123

Default pattern mentionRegEx

/((.)\[([^[]*)]\(([^(^)]*)\))/gi

MentionInput component props

If you prefer to use class component without hooks the MentionInput is for you.

Property nameDescriptionTypeRequiredDefault
valueThe same as in TextInputstringtrue
onChangeThe same as in TextInput(value: string) => voidtrue
partTypesDeclare what part types you want to support (mentions, hashtags, urls)PartType[]false[]
...textInputPropsOther text input propsPartialfalse

Parsing Mention's Value

You can import RegEx that is using in the component and then extract all your mentions from MentionInput's value using your own logic.

import { mentionRegEx } from 'react-native-controlled-mentions';

Or you can use replaceMentionValues helper to replace all mentions from MentionInput's input using your replacer function that receives TriggerData type and returns string.

import { replaceMentionValues } from 'react-native-controlled-mentions';

const value = 'Hello @[David Tabaka](5)! How are you?';

console.log(replaceMentionValues(value, ({ id }) => `@${id}`)); // Hello @5! How are you?
console.log(replaceMentionValues(value, ({ name }) => `@${name}`)); // Hello @David Tabaka! How are you?

Rendering Mention's Value

If you want to parse and render your value somewhere else you can use parseValue tool which gives you array of parts and then use your own part renderer to resolve this issue.

Here is an example:

import {
  Part,
  PartType,
  parseValue,
  isTriggerPartType,
} from 'react-native-controlled-mentions';

/**
 * Part renderer
 *
 * @param part
 * @param index
 */
const renderPart = (
  part: Part,
  index: number,
) => {
  // Just plain text
  if (!part.partType) {
    return <Text key={index}>{part.text}</Text>;
  }

  // Mention type part
  if (isTriggerPartType(part.partType)) {
    return (
      <Text
        key={`${index}-${part.data?.trigger}`}
        style={part.partType.textStyle}
        onPress={() => console.log('Pressed', part.data)}
      >
        {part.text}
      </Text>
    );
  }

  // Other styled part types
  return (
    <Text
      key={`${index}-pattern`}
      style={part.partType.textStyle}
    >
      {part.text}
    </Text>
  );
};

/**
 * Value renderer. Parsing value to parts array and then mapping the array using 'renderPart'
 *
 * @param value - value from MentionInput
 * @param partTypes - the part types array that you providing to MentionInput
 */
const renderValue: FC = (
  value: string,
  partTypes: PartType[],
) => {
  const { parts } = parseValue(value, partTypes);

  return <Text>{parts.map(renderPart)}</Text>;
};

To Do

  • Add support for different text formatting (e.g. URLs)
  • Add more customizations DONE
  • Add ability to handle few mention types ("#", "@" etc) DONE

Known Issues

  • Mention name regex accepts white spaces (e.g. {name: ' ', value: 1})
  • Keyboard auto-correction not working if suggested word has the same length FIXED
  • Text becomes transparent when setting custom font size in TextInput FIXED

Support Me

Buy Me A Coffee

Keywords

FAQs

Last updated on 01 May 2022

Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc