phlie
phlie

Reputation: 1405

React Modifying Textarea Values

I am working on a project which is basically notepad. I am having problems though updating the <textarea>'s value when an ajax call is made. I tried setting the textarea's value property but then no changes to its value can be made. How can I make it so on a state change the textarea's value changes and can be edited.

The code I have is as follows.

In the parent class

<Editor name={this.state.fileData} />

In the Editor class

var Editor = React.createClass({
render: function() {
return (
    <form id="noter-save-form" method="POST">
    <textarea id="noter-text-area" name="textarea" value={this.props.name}></textarea>
    <input type="submit" value="Save" />
    </form>
);
}

});

I can't use defaultValue because the value of the textarea is not known on page load and when I try and put the data between the textareas nothing happens. I would like it to take the state value whenever the state changes but have it editable in between.

Thanks

Edit

I managed to get it working using jQuery but would like to do it in React instead, I called this before render:

$('#noter-text-area').val(this.props.name);

Upvotes: 72

Views: 193169

Answers (4)

ghostb0x
ghostb0x

Reputation: 31

I've recently refactored an app that used the typical value={state} onChange={e => setState(e.target.value)} for my textarea

To now using a useRef hook and removing the value and onChange properties altogether. I update the ref with a custom setter function that updates ref.current.value when I need to update programmatically, but otherwise simply let the user update the textarea value with default html functionality.

I may be re-inventing the wheel a bit, but I expose that ref through React context, and did not want every keystroke to cause re-renders across the app wherever that same context is consumed.

Upvotes: 0

jelmd
jelmd

Reputation: 270

IMHO all the "textarea content as state" non-sense is not needed - not sure, why react handles it different than the input elements. Anyway, usually one wants to set the content of the textarea if an action occurred, which triggers a re-render of the page/textarea anyway. So the solution I use is to set the defaultValue to a function, which set the textarea content.

E.g. I have an input element with a datalist which is backed by an array of json objects. If a user selects an option from the datalist, the idx of the selected option gets set to the state variable selectedIdx. This in turn causes a re-render of the page. When the re-render finally happens, the defaultValue of the textarea gets re-calculated (because it is an expression/function call) and this in turn gets the corresponding datalist[selectedIdx] object and sets the value of the textarea element to the desired value:

export default function PageFragment(): JSX.Element {
// ...
    const [selectIdx, setSelectIdx] = useState(-1);

    function onInput(e: React.CompositionEvent<HTMLInputElement>) {
        e.preventDefault();
        var ev = e.nativeEvent;
        if (!(('inputType' in ev) && (ev.inputType === 'insertReplacementText')))
            return;
        var idx = getIdx(groups, ev.data.trim());
        if (idx >= 0)
            setSelectIdx(idx);
    }

    useEffect(() => {
        // fetch groups and set selectedIdx if there is only one or pre-selected
    }

    function updateTextarea() {
        var s = selectIdx >= 0 ? groups[selectIdx].desc : ''
        var el = document.getElementById('desc') as HTMLInputElement;
        if (el)
            el.value = s;
        return s;
    }
// ...
    return (
        <div id='group'>
            <div id="kform">
<h1>Gruppenverwaltung</h1>
...
<form action={ACTION_URL} method="post" onSubmit={handleSubmit} acceptCharset='utf-8'>
    <section id='group-selection'>
        <div className="kinput">
            <label htmlFor="group">Gruppe</label>
            <input id="group-selector" name="group" type="search" list={dlID}
                onInput={onInput} />
        </div>
        {createDatalist(dlID, groups)}
    </section>
    <section id='update_group'>
    <div className="kinput">
...
        <label htmlFor="desc">Bemerkung</label>
        <textarea id="desc" name="desc" defaultValue={updateTextarea()}/>
...
    </div>
    </section>

    <div className="buttons">
        <button type="submit">Gruppe aktualisieren</button>
    </div>
</form>
            </div>
        </div>
    );
}

So no onChange handler is needed at all - it is absolutely useless, would be just overhead.

Upvotes: -2

Matthew Herbst
Matthew Herbst

Reputation: 32043

I think you want something along the line of:

Parent:

<Editor name={this.state.fileData} />

Editor:

var Editor = React.createClass({
  displayName: 'Editor',
  propTypes: {
    name: React.PropTypes.string.isRequired
  },
  getInitialState: function() { 
    return {
      value: this.props.name
    };
  },
  handleChange: function(event) {
    this.setState({value: event.target.value});
  },
  render: function() {
    return (
      <form id="noter-save-form" method="POST">
        <textarea id="noter-text-area" name="textarea" value={this.state.value} onChange={this.handleChange} />
        <input type="submit" value="Save" />
      </form>
    );
  }
});

This is basically a direct copy of the example provided on https://facebook.github.io/react/docs/forms.html

Update for React 16.8:

import React, { useState } from 'react';

const Editor = (props) => {
    const [value, setValue] = useState(props.name);

    const handleChange = (event) => {
        setValue(event.target.value);
    };

    return (
        <form id="noter-save-form" method="POST">
            <textarea id="noter-text-area" name="textarea" value={value} onChange={handleChange} />
            <input type="submit" value="Save" />
        </form>
    );
}

Editor.propTypes = {
    name: PropTypes.string.isRequired
};

Upvotes: 94

Raf
Raf

Reputation: 7649

As a newbie in React world, I came across a similar issues where I could not edit the textarea and struggled with binding. It's worth knowing about controlled and uncontrolled elements when it comes to react.

The value of the following uncontrolled textarea cannot be changed because of value

 <textarea type="text" value="some value"
    onChange={(event) => this.handleOnChange(event)}></textarea>

The value of the following uncontrolled textarea can be changed because of use of defaultValue or no value attribute

<textarea type="text" defaultValue="sample" 
    onChange={(event) => this.handleOnChange(event)}></textarea>

<textarea type="text" 
   onChange={(event) => this.handleOnChange(event)}></textarea>

The value of the following controlled textarea can be changed because of how value is mapped to a state as well as the onChange event listener

<textarea value={this.state.textareaValue} 
onChange={(event) => this.handleOnChange(event)}></textarea>

Here is my solution using different syntax. I prefer the auto-bind than manual binding however, if I were to not use {(event) => this.onXXXX(event)} then that would cause the content of textarea to be not editable OR the event.preventDefault() does not work as expected. Still a lot to learn I suppose.

class Editor extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      textareaValue: ''
    }
  }
  handleOnChange(event) {
    this.setState({
      textareaValue: event.target.value
    })
  }
  handleOnSubmit(event) {
    event.preventDefault();
    this.setState({
      textareaValue: this.state.textareaValue + ' [Saved on ' + (new Date()).toLocaleString() + ']'
    })
  }
  render() {
    return <div>
        <form onSubmit={(event) => this.handleOnSubmit(event)}>
          <textarea rows={10} cols={30} value={this.state.textareaValue} 
            onChange={(event) => this.handleOnChange(event)}></textarea>
          <br/>
          <input type="submit" value="Save"/>
        </form>
      </div>
  }
}
ReactDOM.render(<Editor />, document.getElementById("content"));

The versions of libraries are

"babel-cli": "6.24.1",
"babel-preset-react": "6.24.1"
"React & ReactDOM v15.5.4" 

Upvotes: 15

Related Questions