Marco Mazzai
Marco Mazzai

Reputation: 67

Word Counter with React

I'm trying to write a word counter with React. The idea is that once you exceed the maximum number of words a warning will appear and prevent the user to insert more characters. My idea was to use the maxlength attribute. Once the words needed and the words written are the same amount, the characters will be counted and the maxlength attribute activated through state.

The maxlength attribute doesn't work properly. How can I fix it?

HTML

<div id="app"></div>

REACT

class MyComponent extends React.Component {
  constructor(props){
    super(props);
    this.state = {
      firstValue: '',
      secondValue: '',
      needWords: '',
      wordCount: '',
      limWords: null,
    }
    this.firstHandle = this.firstHandle.bind(this)
    this.handleSubmit = this.handleSubmit.bind(this)
    this.secondHandle = this.secondHandle.bind(this)    
  }
  firstHandle(e){
    this.setState({
      firstValue: e.target.value
    })
  }

  handleSubmit(e){
    e.preventDefault()
    this.setState({
      needWords: this.state.firstValue
    })
  }

  secondHandle(event){
    this.setState({
      secondValue: event.target.value,
      wordCount: event.target.value === '' ? 0 : this.state.secondValue.split(' ').length,
      limWords: (this.state.needWords - this.state.wordCount) < 0 ? this.state.secondValue.length : null
    })

  }

  render(){
    var result = this.state.needWords - this.state.wordCount;
    let tooManyChars;
    if (result < 0){
      const tooManyCharStyle = {
        color: 'red'
      }
      tooManyChars = <p style={tooManyCharStyle}>You exceeded the maximum number of words!!</p>;
    }
    return(
     <div>
      <form onSubmit={this.handleSubmit}>
        <p>How many words do you have to write?</p>
        <input 
          type="text" 
          value={this.state.firstValue}
          onChange={this.firstHandle}></input>
        <button type="submit">Go</button>
      </form>
      <form>
        <p>You still have to write {result} words</p>
        <textarea 
          type="text"
          value={this.state.value}
          onChange={this.secondHandle}
          maxLength={this.state.limWords}>
        </textarea>
        { tooManyChars }
       </form>
      </div>
    );
  }
}

ReactDOM.render(<MyComponent/>, document.getElementById('app'));

Upvotes: 1

Views: 7158

Answers (3)

Estus Flask
Estus Flask

Reputation: 222999

As explained in this answer, maxlength is applied only to user input, not to programmatically set values.

In order to cover both, secondValue should be truncated with substr either in change handler or in render. maxlength is optional because excessive length will be handled on field change any way.

Upvotes: 0

S--
S--

Reputation: 408

maxLength takes in the maximum number of characters allowed in that field e.g.

<input type="text" maxLength={10} />

will allow a maximum of 10 characters (not words) in the field.

You need to change your onChange method to monitor changes to the field and accept new characters (but not accept spaces as that dictates a new word). So you can add a conditional like so:

secondHandle(event){

    if(event.target.value.split(' ').length > this.state.needWords) {
        event.target.value = event.target.value.trimRight();
    } else {
        this.setState({
            secondValue: event.target.value,
            wordCount: event.target.value === '' ? 0 : this.state.secondValue.split(' ').length,
            limWords: (this.state.needWords - this.state.wordCount) < 0 ? this.state.secondValue.length : null
        });
    }
}

Upvotes: 0

aravind_reddy
aravind_reddy

Reputation: 5476

You have made a couple of mistakes in your code. First thing you need to understand is setState is asynchronous and if you need to use the current values use them directly. for text area you need to pass this.state.secondValue instead of this.state.value and there are couple of other mistakes also. here is the working code.

   class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      firstValue: "",
      secondValue: "",
      needWords: "",
      wordCount: "",
      limWords: null
    };
    this.firstHandle = this.firstHandle.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
    this.secondHandle = this.secondHandle.bind(this);
  }
  firstHandle(e) {
    this.setState({
      firstValue: e.target.value
    });
  }

  handleSubmit(e) {
    e.preventDefault();
    this.setState({
      needWords: this.state.firstValue,
      secondValue: ""
    });
  }

  secondHandle(event) {
    //calculate the word count first itself and use them in other manipulations
    const wordCount =
      event.target.value === "" ? 0 : event.target.value.split(" ").length;
    this.setState({
      secondValue: event.target.value,
      wordCount: wordCount,
      limWords:
        this.state.needWords - wordCount < 0
          ? this.state.secondValue.length
          : null
    });
  }

  render() {
    var result = this.state.needWords - this.state.wordCount;
    let tooManyChars;
    if (result < 0) {
      const tooManyCharStyle = {
        color: "red"
      };
      tooManyChars = (
        <p style={tooManyCharStyle}>
          You exceeded the maximum number of words!!
        </p>
      );
    }
    return (
      <div>
        <form onSubmit={this.handleSubmit}>
          <p>How many words do you have to write?</p>
          <input
            type="text"
            value={this.state.firstValue}
            onChange={this.firstHandle}
          />
          <button type="submit">Go</button>
        </form>
        <form>
          <p>You still have to write {result} words</p>
          <textarea
            type="text"
            value={this.state.secondValue}
            onChange={this.secondHandle}
            maxLength={this.state.limWords}
          />
          {tooManyChars}
        </form>
      </div>
    );
  }
}

here is the live working example:https://codesandbox.io/s/pk085z3rk7

Upvotes: 2

Related Questions