onKeyUp not working in React where onChange did
Asked Answered
C

4

9

I'm doing a React coding challenge that requires a value to be updated onKeyUp. I initially set it to update onChange but the tests require onKeyUp so I tried to change it to that, but my fields are no longer updating and I can't type anything into the textarea.

class MarkdownApp extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      value: ''
    };

    this.handleKeyUp = this.handleKeyUp.bind(this);
  }

  handleKeyUp(event) {
    this.setState({ value: event.target.value })
  }

  render() {
    return (
      <form>
        <label>
          Enter your markdown here:
          <br />
          <textarea value={this.state.value} onKeyUp={this.handleKeyUp} id='editor' />
          <br />
        </label>
        <label>
          Your markup will be previewed here:
          <p id='preview'>{marked(this.state.value)}</p>
        </label>
      </form>
    );
  }
}

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

Like I said, this worked fine when it was onChange and my function was handleChange, but since I switched it I can't type anything.

Carpometacarpus answered 12/7, 2018 at 1:25 Comment(5)
@Carpometacarpus can you post the marked() function?Redeeming
marked is a js library: marked.js.org/#/README.md#README.mdCarpometacarpus
What happens when you console log the event.target.value inside of handleKeyUp?Kiloliter
Just remove value={this.state.value} in the text area and all will be wellRedeeming
I'm not sure what was the coding challenge, but onInput would have done the work with value attribute rather than onKeyUpToler
C
2

Since the event happens before the actual value of the textbox is changed, the result of event.target.value is an empty string. Setting the state with the empty string, clears the textbox.

You need to get the pressed key value from the event, and add it to the existing state.value.

Note: I've removed marked from the demo

class MarkdownApp extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      value: ''
    };

    this.handleKeyUp = this.handleKeyUp.bind(this);
  }

  handleKeyUp(event) {
    const keyValue = event.key;
    
    this.setState(({ value }) => ({
      value: value + keyValue
    }))
  }

  render() {
    return (
      <form>
        <label>
          Enter your markdown here:
          <br />
          <textarea value={this.state.value} onKeyUp={this.handleKeyUp} id='editor' />
          <br />
        </label>
        <label>
          Your markup will be previewed here:
          <p id='preview'>{this.state.value}</p>
        </label>
      </form>
    );
  }
}

ReactDOM.render(
  <MarkdownApp />,
  document.getElementById('root')
);
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>

<div id="root"></div>
Comeau answered 12/7, 2018 at 1:36 Comment(6)
You can identify the press key, and if it's backspace remove the last character. However, you'll have to do it for every special key (arrows, for example), and key combinations. That's why it's a challenge. In the real world, you should use onChange.Comeau
I ran into that yeah. Even though I got onKeyUp to work, the challenge still fails so I'm not sure exactly what's supposed to be happening. I'm gonna create a forum post for them and ask about what's going onCarpometacarpus
You should try the other answers - uncontrolled component, or using ref to update. Might be a better fit for what you need.Comeau
using ref actually worked better, didn't capture the special charactersCarpometacarpus
Or listen to the keyCode of the keyup event and conditionally clear the state based on that keyCode. It really depends what the challenge states exactly...Redeeming
If you press del, back press it will not work...OnChange function is better in this case.Hekker
R
7

I would just remove the value attribute from the textarea. Because if you put the value attribute to it then the user won't be able to change it interactively. The value will always stay fixed(unless you explicitly change the value in your code). You don't need to control that with React--the DOM will hold onto the value for you.

The only change I've made below is to remove value={this.state.value} from the textarea element:

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

class MarkdownApp extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      value: ''
    };

    this.handleKeyUp = this.handleKeyUp.bind(this);
  }

  handleKeyUp(event) {
    this.setState({ value: event.target.value })
  }

  render() {
    return (
      <form>
        <label>
          Enter your markdown here:
          <br />
          <textarea value={this.state.value} onKeyUp={this.handleKeyUp} id='editor' />
          <br />
        </label>
        <label>
          Your markup will be previewed here:
          <p id='preview'>{this.state.value}</p>
        </label>
      </form>
    );
  }
}

ReactDOM.render(
  <MarkdownApp />,
  document.getElementById('root')
);
Redeeming answered 12/7, 2018 at 1:42 Comment(3)
what if we need to control that value?Ducky
ah, in that case you'd want to use a "controlled component" @TanDat. Check this out: reactjs.org/docs/forms.htmlRedeeming
But then you can't use onKeyUp to handle Enter key?Ducky
C
2

Since the event happens before the actual value of the textbox is changed, the result of event.target.value is an empty string. Setting the state with the empty string, clears the textbox.

You need to get the pressed key value from the event, and add it to the existing state.value.

Note: I've removed marked from the demo

class MarkdownApp extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      value: ''
    };

    this.handleKeyUp = this.handleKeyUp.bind(this);
  }

  handleKeyUp(event) {
    const keyValue = event.key;
    
    this.setState(({ value }) => ({
      value: value + keyValue
    }))
  }

  render() {
    return (
      <form>
        <label>
          Enter your markdown here:
          <br />
          <textarea value={this.state.value} onKeyUp={this.handleKeyUp} id='editor' />
          <br />
        </label>
        <label>
          Your markup will be previewed here:
          <p id='preview'>{this.state.value}</p>
        </label>
      </form>
    );
  }
}

ReactDOM.render(
  <MarkdownApp />,
  document.getElementById('root')
);
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>

<div id="root"></div>
Comeau answered 12/7, 2018 at 1:36 Comment(6)
You can identify the press key, and if it's backspace remove the last character. However, you'll have to do it for every special key (arrows, for example), and key combinations. That's why it's a challenge. In the real world, you should use onChange.Comeau
I ran into that yeah. Even though I got onKeyUp to work, the challenge still fails so I'm not sure exactly what's supposed to be happening. I'm gonna create a forum post for them and ask about what's going onCarpometacarpus
You should try the other answers - uncontrolled component, or using ref to update. Might be a better fit for what you need.Comeau
using ref actually worked better, didn't capture the special charactersCarpometacarpus
Or listen to the keyCode of the keyup event and conditionally clear the state based on that keyCode. It really depends what the challenge states exactly...Redeeming
If you press del, back press it will not work...OnChange function is better in this case.Hekker
M
1

You could make the textarea uncontrolled by not giving it the value and simply storing the value in state from a ref instead.

Example (CodeSandbox)

class MarkdownApp extends React.Component {
  ref = null;
  state = {
    value: ""
  };

  handleKeyUp = event => {
    this.setState({ value: this.ref.value });
  };

  render() {
    return (
      <form>
        <label>
          Enter your markdown here:
          <br />
          <textarea
            onKeyUp={this.handleKeyUp}
            ref={ref => (this.ref = ref)}
            id="editor"
          />
          <br />
        </label>
        <label>
          Your markup will be previewed here:
          <p id="preview">{marked(this.state.value)}</p>
        </label>
      </form>
    );
  }
}
Multicellular answered 12/7, 2018 at 1:37 Comment(3)
what is the benefit of using ref here?Redeeming
This works perfectly (although slower than onChange) although the tests still fail so I'll need to message the forums over there to see what's up. I'm also curious, what exactly is ref doing here?Carpometacarpus
@Carpometacarpus It doesn't seem to be necessary to use the ref. Just removing value={this.state.value} from the code in your question should work. I did not know the onKeyUp event worked like that.Multicellular
K
1

The issue is you have a two way binding with the state = to the value in your textbox. OnChange would update your state after a change is made and the events are done firing. Onkeyup returns the value onkeyup and since you mapped that to your state it will stay as nothing. Remove the value prop and it should work.

Kiloliter answered 12/7, 2018 at 1:45 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.