stackoverflow_user
stackoverflow_user

Reputation: 301

How to list all suggestions and filtered suggestions based on user input using reactjs?

i want to show all available usernames when user types @ in input field and filtered usernames when user enters anything after @ character.

I have implemented like below,

class UserMention extends React.purecomponent {
    constructor(props) {
        super(props);
        this.state = {
            text: '',
            user_mention: false,
        };
        this.user='';
     }

     user_list = [
         {name: 'John smith'},
         {name: 'Jenna surname2'},
         {name: 'Tuija rajala'},
     ]; 

     get_user = s => s.includes('@') && s.substr(s.lastIndexOf('@') + 
     1).split(' ')[0];

     handle_input_change = (event) => {
         let user_mention;
         this.user = this.get_user(event.target.value);

         if (event.target.value.endsWith('@')) {
             user_mention = true;
         } else {
            user_mention = false;
         }

         this.setState({
             user_mention: user_mention,
             [event.target.name]: event.target.value,
         });
      };

      get_text_with_user_mention = (text, selected_user) => {
          let user_name = selected_user;
          let text_without_user_mention;
          text_without_user_mention = text.slice(0, 
          text.lastIndexOf('@'));
          return text_without_user_mention + user_name;
      };

      handle_select_value = (selected_user) => {
          let text;
          text = this.get_text_with_user_mention(this.state.text, 
          selected_user);
          this.setState({
              text: text,
              user_mention: false,
          });
          this.user = false;
      };

      render = () => {
          let suggested_values = [];
          if (this.state.user_mention) {
              suggested_values = this.user_list
                 .map((o) => { return {user_name: o.user_name};});
          }
          if (this.user) {
              suggested_values = this.user_list
                  .filter(user => user.user_name.indexOf(this.user) !== 
                  -1)
                  .map((o) => {return {user_name: o.user_name};});
       }
       return (
            <input
                required
                name="text"
                value={this.state.text}
                onChange={this.handle_input_change}
                type="text"/>
            {this.state.user_mention &&
                <SelectInput
                    on_change={this.handle_select_value}
                    values={suggested_values}/>}
            {this.user &&
                <SelectInput
                    on_change={this.handle_select_value}
                    values={suggested_values}/>}
        );
    };
}

As you see from above code, i am modifying suggested_values based on this.user and this.state.user_mention state. Can someone help me refactor or modify this a bit more nicer. thanks.

Upvotes: 2

Views: 874

Answers (2)

Cat_Enthusiast
Cat_Enthusiast

Reputation: 15698

You can simplify your code by doing something like this.

See sandbox: https://codesandbox.io/s/react-example-kgm2h

import ReactDOM from "react-dom"; import React from "react";

class UserMention extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      text: "",
      user_list: [
        { name: "John smith" },
        { name: "Jenna surname2" },
        { name: "Tuija rajala" }
      ],
      suggestions: []
    };
  }

  handleOnChange = e => {
    const { value } = e.target;
    const { user_list } = this.state;
    //show all user suggestions
    if (value.includes("@") && value.indexOf("@") === value.length - 1) {
      this.setState({
        text: value,
        suggestions: [...this.state.user_list]
      });
      //show matching user suggesstions
    } else if (value.includes("@") && value.length > 1) {
      const stringAfterAt = value.slice(value.indexOf("@") + 1).toLowerCase();
      const newSuggestions = user_list.filter(user => {
        return user.name.toLowerCase().includes(stringAfterAt);
      });
      this.setState({
        text: value,
        suggestions: newSuggestions
      });
      //display no users if they do not use the @ symbol
    } else {
      this.setState({
        text: value,
        suggestions: []
      });
    }
  };

  createSuggestionsList = () => {
    const { suggestions } = this.state;
    return suggestions.map(user => {
      return <div>{user.name}</div>;
    });
  };

  render = () => {
    return (
      <div>
        <input
          required
          name="text"
          value={this.state.text}
          onChange={this.handleOnChange}
          type="text"
        />
        {this.createSuggestionsList()}
        {/* <SelectInput value={this.state.suggestions}/> */}
      </div>
    );
  };
}

ReactDOM.render(<UserMention />, document.getElementById("root"));

I'm not entirely sure how you want to render the suggested users, but you can always just pass down this.state.suggestions as a prop to the SelectInput component.

Main takeaway is to use an additional array in our state for suggestions and update it as the user types into the input. We call {this.createSuggestionsList()} inside render to dynamically create the markup for each suggested user. Or as mentioned above, just pass down the suggestions as a prop.

Upvotes: 0

cbdeveloper
cbdeveloper

Reputation: 31485

This is another approach using React hooks, instead of classes. If you've never worked with hooks, give it a try. You will enjoy it. It's much simpler in my opinion.

enter image description here

I also added a username property. It's much better if you work with a string that doesn't allow spaces when you're tagging someone. You can also display the full name with spaces along with the username, if you wish.

Ex:

John Smith (@johnsmith)

function App() {

  const inputRef = React.useRef(null);
  const [inputValue, setInputValue] = React.useState('');
  const [userList,setUserList] = React.useState([
    {name: 'John smith', username:'johnsmith'},
    {name: 'Jenna surname2', username:'jennasurname2'},
    {name: 'Tuija rajala', username:'tuijarajala'}
   ]
  );
  const [showSuggestions,setShowSuggestions] = React.useState(false);
  
  const [suggestionList,setSuggestionList] = React.useState(
    ['johnsmith','jennasurname2','tuijarajala']
  );
  
  function onChange(event) {
    const regexp = /@[a-zA-Z0-9]*$/;
    if (regexp.test(event.target.value)) {
      setShowSuggestions(true);
    }
    else {
      setShowSuggestions(false);
    }
    setInputValue(event.target.value);
  }
  
  function focusInput() {
    inputRef.current.focus();
  }

  return(
    <React.Fragment>
      <input ref={inputRef} type='text' value={inputValue} onChange={onChange}/>
      {showSuggestions && 
        <Suggestions
          inputValue={inputValue}
          suggestionList={suggestionList}
          applyMention={onChange}
          focusInput={focusInput}
        />
      }
    </React.Fragment>
  );
}

function Suggestions(props) {
  
  function selectSuggestion(username) {
    const regexp = /@[a-zA-Z0-9]*$/;
    const newValue = props.inputValue.replace(regexp,username + ' ');
    props.applyMention({target: {value: newValue}}); // THIS MIMICS AN ONCHANGE EVENT
    props.focusInput();
  }
  
  const suggestionItems = props.suggestionList.map((item) => 
    <div className="item" onClick={()=>selectSuggestion('@' + item)}>@{item}</div>
  );
  
  return(
    <div className="container">
      {suggestionItems}
    </div>
  );
}


ReactDOM.render(<App/>, document.getElementById('root'));
.container {
  border: 1px solid silver;
  width: 150px;
}

.item {
  cursor: pointer;
}

.item:hover {
  color: blue;
}

input {
  width: 300px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
<div id="root"/>

Upvotes: 2

Related Questions