ReactJS - Recipe Box - getting a pre-populated form to accept new user input - is there a better way?

The problem I ran into after finally getting a form to accept a default value (in the Edit overlay div) is that all of a sudden the form no longer accepted user input.

For instance, the form field displays “Spaghetti.” But then you cannot type over it.

(Funny enough, if the form is NEVER given a default value (such as in the Add overlay div), it automatically accepts user input!)

After a lot more research I got the form to accept user input. But the solution seems convoluted and hard to explain. Is there a simpler way just to get the dang form to accept user input after it gets assigned a default value?

The CodePen for this is here.

var GenerateRecipesFromList= React.createClass({
   getInitialState: function(){
     const defaultData = [["Spaghetti", "pasta, oil, sauce, parsely, cheese"], ["PB&J", "PB, J"]]
     const localData = JSON.parse(localStorage.getItem('reclist'));
     return {
      reclist: localData ? localData : defaultData,
      recepi: ''
     }
   },  

   updateRecList: function (reclist) {
     this.setState({ reclist: reclist });
   },


  overlayEdit: function(tempvalue) {
   this.setState({recepi: tempvalue})
   var popup = document.getElementById("editme");
    popup.style.visibility = (popup.style.visibility === "visible") ? "hidden" : "visible";
   },

   handleUserInput: function(theinput){
    var teststring = theinput.toString();
    if(teststring === "recipi"){
   this.setState({
       recipi: recipi
    })
    }
   },

  handleRecipiChange: function(){
     this.props.onUserInput(this.refs.recipi.value);  
  },

   render: function(){
    var testData = JSON.parse(localStorage.getItem('reclist'));
   if(testData === null){
     localStorage.setItem('reclist', JSON.stringify(this.state.reclist));
     }
     var currentData = JSON.parse(localStorage.getItem('reclist'));
     var rows = [];

      for(var i=0; i<currentData.length; i++){
     var thedivname = i;
     var temptitle=currentData[i][0];
      rows.push(<div id= {this.thedivname} className="individual" onClick={this.overlayEdit.bind(this, temptitle)}> <span><h2>{this.state.reclist[i][0]}</h2></span> 
      </div>);
     }
   return(
    <div className="centerMe" >
      <AddButton updateRecList={ this.updateRecList } />
      {rows}

      <div id="editme" recipi={this.state.recipi} onUserInput={this.handleUserInput}>
        <div>
          <form > 
            <p>Edit an existing recipe.</p>
            Recipe Title: <input type="text" name="frank" id="hannah" ref="recipi" onChange={this.handleRecipiChange} value={this.state.recepi}/>
            <br/>
            Ingredients: <input type="text" name="ingred2" id="hannahssister"/><br/>
          </form>
          <p>Click here to <a href='#' onClick={this.overlayEdit}>close</a></p>

        </div>
      </div> 

     </div>
   );
  },
 });

 var AddButton = React.createClass({

   overlayAdd: function() {
    var el = document.getElementById("overlay");
   el.style.visibility = (el.style.visibility === "visible") ? "hidden" : "visible";
   },

    exposeAddRecipe: function(){
      var exposeCurrentData = [];
      var userInput = [];
      exposeCurrentData = JSON.parse(localStorage.getItem('reclist'));
     var newTitle = document.getElementById("title").value;
     var newIngredients = document.getElementById("ingredients").value;

     userInput.push(newTitle);
     userInput.push(newIngredients);
     exposeCurrentData.push(userInput);
      localStorage.setItem('reclist', JSON.stringify(exposeCurrentData));
      //this.setState({ reclist: exposeCurrentData});
    this.props.updateRecList(exposeCurrentData);
     this.overlayAdd();
   },

   render: function(){
   return(
    <div>
      <button type="button" id="btnAdd" onClick={this.overlayAdd}><h2>Add a New Recipe</h2></button> 
        <div id="overlay">
         <div>
            <form > 
             <p>Add a new recipe.</p>
             Recipe Title: <input type="text" name="titl" id="title">

              </input><br/>
             Ingredients: <input type="text" name="smith" id="ingredients" /> <br/>
           <button type="button" className="normalBtn" onClick={this.exposeAddRecipe}>Save</button>
           </form>
           <p>Click here to <a href='#' onClick={this.overlayAdd}>close</a></p>

          </div>
        </div> 
      </div>
      );
     }
   });

 var Footer = React.createClass({
   render() {
  return (
   <footer>
    <div id="containerfooter">
      <p>Written by <a href="http://codepen.io/profaneVoodoo/full/dXBJzN/">John Gillespie</a> for FreeCodeCamp Campers. Happy Coding!</p>
    </div>
   </footer>
   );
   }
   });


 var MyApp = React.createClass({  
 render: function() {
 return(
  <div className = "mainDiv">
      <div className="titleDiv">
    <h1>Recipe Box</h1>

       <GenerateRecipesFromList />
       <Footer />
      </div>        
    </div>
   );
  }, 
}); 

 ReactDOM.render(
  <MyApp />,
  document.getElementById('Recipes')
);

Hi, looking through your code it seems you know more about react than I, so if my answer is a bit simple, don’t be too harsh on me, just trying to help!
I think you need to set the value for the field in your (parent?) component state, then, call an update function to change the state value :

<input type="text" value={this.props.my_field_value} onChange={this.props.myFieldChanged} />

hope this helps.

1 Like

Yeah, I just removed a bunch of what looks like unnecessary code and it still seems to be allowing me to type over the default value. I am actually don’t want to change state just yet! That will be handled later when I add a function that exposes the localStorage for updating, similar to the function I already have for saving a new recipe.

var GenerateRecipesFromList= React.createClass({
getInitialState: function(){
  const defaultData = [["Spaghetti", "pasta, oil, sauce, parsely, cheese"], ["PB&J", "PB, J"]]
  const localData = JSON.parse(localStorage.getItem('reclist'));
  return {
    reclist: localData ? localData : defaultData,
    recepi: ''
  }
 },  

updateRecList: function (reclist) {
  this.setState({ reclist: reclist });
},


overlayEdit: function(tempvalue) {
  this.setState({recepi: tempvalue})
    var popup = document.getElementById("editme");
  popup.style.visibility = (popup.style.visibility === "visible") ? "hidden" : "visible";
},


 handleRecipiChange: function(){
  this.props.onUserInput(this.refs.recipi.value);  
},

 render: function(){
  var testData = JSON.parse(localStorage.getItem('reclist'));
  if(testData === null){
      localStorage.setItem('reclist', JSON.stringify(this.state.reclist));
      }
      var currentData = JSON.parse(localStorage.getItem('reclist'));
      var rows = [];

      for(var i=0; i<currentData.length; i++){
     var thedivname = i;
     var temptitle=currentData[i][0];
      rows.push(<div id= {this.thedivname} className="individual" onClick={this.overlayEdit.bind(this, temptitle)}> <span><h2>{this.state.reclist[i][0]}</h2></span> 
    </div>);
     }
   return(
     <div className="centerMe" >
       <AddButton updateRecList={ this.updateRecList } />
       {rows}

       <div id="editme" >
        <div>
          <form > 
            <p>Edit an existing recipe.</p>
            Recipe Title: <input type="text" name="frank" id="hannah" ref="recipi" onChange={this.handleRecipiChange} value={this.state.recepi}/>
            <br/>
            Ingredients: <input type="text" name="ingred2" id="hannahssister"/><br/>
          </form>
          <p>Click here to <a href='#' onClick={this.overlayEdit}>close</a></p>

         </div>
       </div>

Have a look here: https://facebook.github.io/react/docs/forms.html#controlled-components

A controlled <input> has a value prop. Rendering a controlled <input> will reflect the value of the value prop.

  render: function() {
    return <input type="text" value="Hello!" />;
  }

User input will have no effect on the rendered element because React has declared the value to be Hello!. To update the value in response to user input, you could use the onChange event:

I did not have time to look into your code, but remembered this topic when reading the react tutorial. See if it helps you.

1 Like

Yup, that’s exactly what the problem was. If the form comes with “value=‘abc’” then typing in that form does nothing; the value is set. Adding an onChange event however makes the field editable.

If no value is assigned to the form, the field accepts user input without having to involve an event handler.

Now, it would get more complicated if I wanted to setState as the user types stuff in the field, but instead I will use a separate function that will among other things do a setState when the user clicks a Save button.