Search code examples
javascriptreactjsemoji

Get text after `:` while using keypress event to build Slack-style Emoji in React?


I want to create Slack-style emoji that shows up when you start using :

I have created a Stackblitz reproduction -> https://stackblitz.com/edit/react-ts-uucibv?file=index.tsx

Here's what I did:

import React, { Component } from 'react';
import { render } from 'react-dom';
import React from 'react';
import ES from 'emoji-set';

const TextArea = () => {
  const [text, setText] = React.useState('');
  const [openEmoji, setOpenEmoji] = React.useState(false);

  const keyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
    if (e.key === ':') {
      setOpenEmoji(true);
    } else {
      setOpenEmoji(false);
    }
  };

  const textChanged = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
    const newText = e.target.value;

    if (openEmoji) {
      console.log(ES.getKeywords(newText));
    }

    setText(newText);
  };

  return (
    <div>
      <textarea
        value={text}
        onKeyDown={keyDown}
        onChange={textChanged}
        placeholder="Type here..."
        spellCheck={false}
        style={{
          width: '350px',
          height: '50vh'
        }}
      />
    </div>
  );
};

class App extends Component {
  render() {
    return (
      <div>
        <h1>React Slack Emoji</h1>
        <TextArea />
      </div>
    );
  }
}

render(<App />, document.getElementById('root'));

I created a simple textarea that detects keypresses. I detect when : is pressed but I am confused as to how do I get everything after a :

I am using https://www.npmjs.com/package/emoji-set

I'm calling ES.getKeywords right when I press : but I need some way to get everything after keywords. For example, if I write :c then I should be returned ca, cabbage, cactus & all other keywords starting from c. However, I am unable to get everything after : perfectly.

I want the emoji-picker to be fast & performant as well as show the emoji's typed right there.

Currently, I get returned every Emoji when I search : as the string is empty.

How do I do it?


Solution

  • Couple of things have to change. First, your assumption that each key down should control your emoji state is off because :c as in your example requires two key presses. The first sets the emoji state, then the second immediately clears it causing a problem. Second, your dependency says that the proper method to call is searchByKeyword not getKeywords.

    In my code I have an assumption that when you are editing an emoji and press space, the emoji processing gets stopped. But basically you have to change from a single key press triggering checking to be a toggle that checks while the : condition is met.

    import React, { Component } from 'react';
    import { render } from 'react-dom';
    import React from 'react';
    import ES from 'emoji-set';
    
    const TextArea = () => {
      const [text, setText] = React.useState('');
      const [openEmoji, setOpenEmoji] = React.useState(false);
    
      const keyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
        // changed
        if (e.key === ':' && !openEmoji) {
          setOpenEmoji(true);
        } else if (e.key === ' ' && openEmoji) {
          setOpenEmoji(false);
        } else if (e.key === ':' && openEmoji) {
          setOpenEmoji(false);
        }
      };
    
      const textChanged = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
        const newText = e.target.value;
        const emojiToSearch = newText.split(/:/).pop(); // changed
        
        // changed
        if (openEmoji && emojiToSearch && emojiToSearch.length > 1) {
          console.log(ES.searchByKeyword(emojiToSearch));
        }
    
        setText(newText);
      };
    
      return (
        <div>
          <textarea
            value={text}
            onKeyDown={keyDown}
            onChange={textChanged}
            placeholder="Type here..."
            spellCheck={false}
            style={{
              width: '350px',
              height: '50vh'
            }}
          />
        </div>
      );
    };
    
    class App extends Component {
      render() {
        return (
          <div>
            <h1>React Slack Emoji</h1>
            <TextArea />
          </div>
        );
      }
    }
    
    render(<App />, document.getElementById('root'));
    

    Modified stackbliz