Nancy Moore
Nancy Moore

Reputation: 2470

How to properly display user is typing using ReactJS

The code below shows user is typing when someone starts typing on the form input.

Below is what am trying to accomplish now:

How do I add timeout like 5 seconds so that the applications can only show user typing alert only for 5 seconds whenever user starts typing in a form inputs (just like in chat typing notification).

I guess it has to do with timeout function like code below:

setTimeout(()=>{
  this.sate.data
},5000);

Is there a better approach to this?

Here is the code so far:

class App extends React.Component {
  constructor(props) {
    super(props)
    this.state = { data: [], email: '' };
    this.handleChange1 = this.handleChange1.bind(this);
    this.onKeyPressed = this.onKeyPressed.bind(this);
  }

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

  onKeyPressed(event) {
    console.log(event.key);
    this.setState({
      data: [{ id: '1', name: 'Tony' }]
    });
  }

  render() {
    return (
      <div>
        <h1>Display User is Typing.....</h1>
        <label>
          <input
            onKeyDown={event => this.onKeyPressed(event)}
            value={this.state.email}
            onChange={this.handleChange1}
            name="email"
          />
        </label>
        <ul>
          {this.state.data.map((person, i) => {
            if (person.id == 1) {
              console.log('am typing.......');
              return (
                <div key={i}>
                  <div>{person.name} is typing.....</div>
                </div>
              );
            }
          })}
        </ul>
      </div>
    );
  }
}

Upvotes: 1

Views: 3565

Answers (2)

Olaniyi Olabode
Olaniyi Olabode

Reputation: 21

Here is a version of the Hooks version of this answer... I found the answer very useful

import { useState } from "react";
import { Form, Button, FormControl, InputGroup } from "react-bootstrap";
import debounce from "lodash/debounce";

const SendMessageForm = ({ sendMessage, messages }) => {
  const [message, setMessage] = useState("");
  const [isTyping, setIsTyping] = useState(false);
  // const [currentUserTyping, setCurrentUserTyping] = useState("");

  // useEffect(() => {
  //   checkUserTyping();
  // }, [messages]);

  const handleIsTyping = debounce(function () {
    // continually delays setting "isTyping" to false for 500ms until the user has stopped typing and the delay runs out
    setIsTyping(false);
  }, 500);

  // const checkUserTyping = () => {
  //   const user = messages.map((m) => m.user);
  //   setCurrentUserTyping(user);
  //   console.log(user);
  // };

  return (
    <Form
      onSubmit={(e) => {
        e.preventDefault();
        sendMessage(message);
        setMessage("");
      }}
    >
      <InputGroup>
        <FormControl
          type="user"
          placeholder="message..."
          onChange={(e) => {
            setMessage(e.target.value);
            setIsTyping(true);
            handleIsTyping();
          }}
          name="message"
          value={message}
        />
        <span className="user-typing">
          {isTyping && `a user is typing....`}
        </span>
        <InputGroup.Append>
          <Button variant="primary" type="submit" disabled={!message}>
            Send
          </Button>
        </InputGroup.Append>
      </InputGroup>
    </Form>
  );
};

export default SendMessageForm;

Upvotes: 2

Matt Carlotta
Matt Carlotta

Reputation: 19782

You can use lodash's debounce function, which will delay an action for a specified amount of ms. For example, if a user is typing, it'll continually reset the delay. When the user stops typing, the delay runs out and triggers the action. (also, please read my note below the example code)

You can scale up the example below to include the user's name by mapping over your data in a container and passing it down to a reusable component.

Working example: https://codesandbox.io/s/j3n6q98m0w

components/App/App.js

import debounce from "lodash/debounce";
import React, { Component } from "react";

export default class App extends Component {
  constructor() {
    super();
    this.state = { isTyping: false, test: "" };
    this.handleChange = this.handleChange.bind(this);
  }

  handleChange({ target: { name, value } }) {
    this.setState({ isTyping: true, [name]: value }, () => { // allows user input updates and continually sets "isTyping" to true
      this.handleTyping();
    });
  }

  handleTyping = debounce(function() { // continually delays setting "isTyping" to false for 500ms until the user has stopped typing and the delay runs out
    this.setState({ isTyping: false });
  }, 500);

  render() {
    return (
      <div className="app-container">
        <h1>Capturing User Input</h1>
        <input
          className="uk-input"
          placeholder="Type something..."
          type="text"
          name="test"
          value={this.state.test}
          onChange={this.handleChange}
        />
        <p className="user-typing">
          {this.state.isTyping && "User is typing..."}
        </p>
      </div>
    );
  }
}

Note: You don't need to create an anonymous function inside the render method:

onKeyDown={(event) => this.onKeyPressed(event)}

Instead, it'll just be:

onKeyDown={this.onKeyPressed}

By default onKeyDown sends an event to the callback onKeyPressed:

onKeyPressed(e){
  console.log(e.keyCode);
}

The only time you'll need to create an anonymous function is when you want to include a value in addition to the event:

onKeyDown={(event) => this.onKeyPressed(event, this.state.value)}

Then your callback onKeyPressed will accept two parameters:

onKeyPressed(e, val){
  console.log(e.keyCode);
  console.log(val);
}

Upvotes: 1

Related Questions