Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I make a React component editable?

At the last stage of my project for learning react and firebase and I am facing a problem. From reading the docs of both of those I have understood how and what to do for the most part.

I want to take in the content from a blog post and then edit it and post it back using the

update()

method firebase has. As of now, the two errors I am facing are that I am unable to edit that content as react says that it is a controlled element which I understand from the docs is cause I passed in it as a value.

But when I tried to remove that and instead create a setState in an onInputChange like the docs suggested it gets updated but with blank content and the original text is missing as well from the input field. Which means it something I am doing wrong with react but I’m not sure what it is.

  constructor(props){
    super(props);
    this.state =  {
      title: '',
      body: '',
      post:{},
      
  };
    this.onInputChange = this.onInputChange.bind(this);
    this.onHandleUpdate = this.onHandleUpdate.bind(this);
}

  
  componentDidMount(){
    database.ref(`posts/${this.props.match.params.postId}`) 
    .on('value', snapshot => {
          this.setState({post: snapshot.val()});
    });
    this.postId= this.props.match.params.postId;
    
}
  onInputChange(e){
    this.setState({
       title: ‘’, body:''


    });
  }

  onHandleUpdate(e){
    console.log(this.postId);
    e.preventDefault();
    database.ref('posts').child(`${this.postId}`).update(this.state); 
    this.setState({
      title: '',
      body: ''    
    });
  }

  render() {
    return (
 
        <div className="container">
          <form onSubmit={this.onHandleUpdate}>
            <div className="form-group">
              <input value={this.state.title && this.state.post.title}  className="form-control" type="text" name="title" ref="title" onChange={this.onInputChange}/>
            </div>
              <br/>
            <div className="form-group">  
              <input value={this.state.body && this.state.post.body} className="form-control" type="text" name="body" ref="body" onChange={this.onInputChange} />
              <br/>
            </div>
            <button  className="btn btn-success">Submit</button>
          </form>
        </div>
      
    );
  }
    
    
    
}

This is the how the code is set up at the moment. The summary of the problem simply comes down to why I am unable to change the value in the text box. What am I doing wrong please?

like image 668
Al-m Avatar asked Feb 11 '18 16:02

Al-m


People also ask

How to create a component in react?

When creating a React component, the component's name must start with an upper case letter. The component has to include the extends React.Component statement, this statement creates an inheritance to React.Component, and gives your component access to React.Component's functions.

How to build a react web application?

Building a React web application, for the most part, is writing components which combine to form your user interface. But there are certain components which are reused across your entire application — Button, Link, Dropdown, Tooltip, etc. For larger projects, all these components reside in a common style guide.

How do I make an element editable in react?

Using Content Editable Elements in JavaScript (React) Any element can be made editable with the addition of the contenteditable attribute. This attribute is used all over the web, such as in Google Sheets.

What's new in react 16?

It is now suggested to use Function components along with Hooks, which were added in React 16.8. There is an optional section on Class components for your reference. When creating a React component, the component's name MUST start with an upper case letter. A class component must include the extends React.Component statement.


2 Answers

If you'd like to make an input editable controlled input in react create the input with a value and a way to change that value:

<input
 type="text"
 value={this.state.title}
 onChange={e => this.handleInputChange(e)}
/>

In your code above you have defined a pair of onChange handlers but you don't pass the event into them so they never have any data to work with.

For my example, my onChange handler could be:

handleInputChange(e) {
    this.setState({
      title: e.target.value
    });
  }

Whenever I type into my input it will change the state of the component and that will change what I see in the input. It's a little indirect but that's how controlled components work in react.

If you add a new Input, then you can create a new onChange handler and everything should work fine. This approach gets complicated quick though. Create a form with 10 inputs and suddenly you have a hoard of functions to look after in your component.

A more manageable approach would be to create a single onChange handler that works with every input component. You can create this by passing the name of the state field you want to update into the function.

 handleChange(e, field) {
    this.setState({
      [field]: e.target.value
    });
  }

Then in your input you can use it like so:

<input
          type="text"
          value={this.state.title}
          onChange={e => this.handleChange(e, "title")}
        />

The field parameter is a string of the name of the field in state that you want that input to update.

This means you can reuse the same method for the body input like so:

  type="text"
  value={this.state.body}
  onChange={e => this.handleChange(e, "body")}
/>

You can delete the onTitleChange and onBodyChange methods and replace them with the handleChange method above. Much simpler. The less moving parts there are, the fewer things there are to break.

When you are ready to submit your form you can use the desired values in state in your onHandleUpdate method like so:

onHandleUpdate(e){
    e.preventDefault();
    database.ref('posts').child(`${this.postId}`).update({
       title: this.state.title,
       body: this.state.body
    }); 
    this.setState({
      title: '',
      body: ''    
    });
  }

There is no need to do the whole nesting thing you are doing with the post value in state. You can nest to your heart's content, but while you're still learning just keep things easy to reason about by keeping as much as you can un-nested in state.

database.ref(`posts/${this.props.match.params.postId}`) 
    .on('value', snap =>  this.setState({
       title: snap.val().title, 
       body: snap.val().body 
    }));

I made a little sandbox to show that all this works. Have a look at the hello component here. I've simplified it a bit and I had to replace the database calls (since I don't have your firebase keys) but it should make sense.

like image 104
Josh Pittman Avatar answered Oct 07 '22 01:10

Josh Pittman


As they are two seperate field values you need to handle them in two seperate functions. You cannot do in the same method onInputChange. you may change your code as below.

constructor(props){
    super(props);
    this.state =  {
      post:{
             title: props.post.title || '',
             body: props.post.body|| '',
     },

  };

    this.onTitleChange = this.onTitleChange .bind(this);
    this.onBodyChange = this.onBodyChange .bind(this);
    this.onHandleUpdate = this.onHandleUpdate.bind(this);
}


  componentDidMount(){
    database.ref(`posts/${this.props.match.params.postId}`) 
    .on('value', snapshot => {
          this.setState({post: snapshot.val()});
    });
    this.postId= this.props.match.params.postId;

}
  onTitleChange(e){
    this.setState({

      post: { ...this.state.post,
       title: e.target.value}
    });
  }

  onBodyChange(e){
    this.setState({
      post: { ...this.state.post,
       body: e.target.value}
    });
  }

  onHandleUpdate(e){
    console.log(this.postId);
    e.preventDefault();
    database.ref('posts').child(`${this.postId}`).update(this.state); 
    this.setState({
      title: '',
      body: ''    
    });
  }

  render() {
    return (

        <div className="container">
          <form onSubmit={this.onHandleUpdate}>
            <div className="form-group">
              <input value={this.state.post.title}  className="form-control" type="text" name="title" ref="title" onChange={this.onTitleChange}/>
            </div>
              <br/>
            <div className="form-group">  
              <input value={this.state.post.body} className="form-control" type="text" name="body" ref="body" onChange={this.onBodyChange} />
              <br/>
            </div>
            <button  className="btn btn-success">Submit</button>
          </form>
        </div>

    );
  }

or if state is set from componentDidMount your constructor may be like this

constructor(props){
    super(props);
    this.state =  {
      post: null,  //hope you post contains title and body {title: 'sometitle', body: 'somebody'}
  };
like image 37
Kodanda Rama Durgarao poluri Avatar answered Oct 07 '22 01:10

Kodanda Rama Durgarao poluri