React JS Recipe Box - passing prop to function

I am able to pass the “recipi” prop to the “editme” div that gets rendered when the user clicks on a particular recipe. However, I can;t figure out how to subsequently pass the “recipi” prop to the exposeEditRecipe that gets called with the “Click here to Save” button’s onClick event.

I’ve tried several different ways to pass “recipi” as a property bound to onClick={this.exposeEditRecipe}, but nothing so far works

The only way I have been able to get the exposeEditRecipe to “see” what the value associated with “recipi” was at the time the function was called is to hide that value on the “editme” div in a hidden field!

This works. But it’s silly.

What is a better way to do this?

My 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";
   },


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

     exposeEditRecipe: function(){
      var exposeCurrentData2 = [];
       exposeCurrentData2 = JSON.parse(localStorage.getItem('reclist'));
      var newTitle2 = document.getElementById("hannah").value;
      var secret = document.getElementById("hiddenhidden").value;
     console.log(secret);

         for(var x = 0; x<exposeCurrentData2.length; x++ ){
           if(exposeCurrentData2[x][0]===secret){
             exposeCurrentData2[x][0]=newTitle2;
            exposeCurrentData2[x][1]=newTitle2;
           }
         }

       localStorage.setItem('reclist', JSON.stringify(exposeCurrentData2));
       this.updateRecList(exposeCurrentData2);
      },

  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>
             <input type="text" name="hiddenlabel" id="hiddenhidden" ref="recipi" value={this.state.recepi} hidden/>
             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.exposeEditRecipe}>Save</a></p>
          <p>Click here to <a href='#' onClick={this.overlayEdit}>close</a></p>

         </div>
       </div> 

     </div>
    );
   },
 });

Try splitting up your app into smaller components. So as you have done with the Add Button component, make another component for Edit Recipe and pass down the props rather than passing data within one component. As a guide I had 9 components and 3 or 4 levels in my recipe app, it can be a little long passing down props without something like Redux but I kept my recipe state and functions such as edit, add and delete in the top-level app and then set up components for each of those actions and passed down the necessary props to each one.

For example don’t render any html in your top level app, just render a child component (something like RecipesList) passing down your state as props to it. Then split RecipesList into RecipeTitle and RecipeIngredients. At this level is where I put edit and delete components because they need to access to the recipe title and ingredients

I find it helps to think about the structure of the app a little first and what it’s going to do. Start with a top level app component and then divide everything you can think of into components. Some components will change state (edit, add, delete), other stateless components (recipe list, ingredient list) simply render data passed down to them.

1 Like

I have updated the code but have run into a snag. The very bottom component (the one that actually appears at the top of the code, “EditOverlay”) keeps getting passed the wrong property. For example, if the PB&J tile is clicked on, Recipe Title in the overlay still says “Spaghetti.”

It needs to be passed the recipe title from the div that was clicked on. What is happening now is it is getting passed the recipe title of the very first div, no matter which div is clicked on.

What am I missing? Or should I take another design approach?

The CodePen for this is here.

var EditOverlay = React.createClass({
  render: function(){
  return(
      <div id="editme">
      <div>
      <form > 
            <p>Edit an existing recipe.</p>
            Recipe Title: <input type="text" name="frank" id="hannah"   value={this.props.currentitle}/>
            <br/>  
      </form>
        </div>
    </div>
     );
      }
  });

var RecipeTiles = React.createClass({

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


 render: function(){
   return(
     <div target = {this.props.titleofrecipe} onClick = {this.overlayEdit}>
       {this.props.titleofrecipe}<br/>
       <EditOverlay currentitle = {this.props.titleofrecipe} />
     </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 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,
    }
  },  

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

  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];
     var tempingred = currentData[i][1];     
      rows.push(<div id= {this.thedivname} className="individual" > <RecipeTiles updateRecList={ this.updateRecList } titleofrecipe={temptitle} listofingredients={tempingred} /> 
     </div>);
     }
   return(
     <div className="centerMe" >
       <AddButton updateRecList={ this.updateRecList } />
       {rows}
     </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')
 );

I would change the structure of your recipe data from an array of arrays to an array of objects the same as JSON then you can give each recipe an id and use that in your edit and delete components. At the moment each of your recipes has no unique id. Arrays are index-based so at the moment you have 2 recipes in your array ‘Spaghetti’ is [0] and ‘PB&J’ is [1]. However, if you delete the spaghetti recipe then the PB&J recipe will become [0]. Objects are easier to work with because they are not index-based so a recipe’s position can move but it’s id will be unchanged

This would be a better way to set up your recipes

const defaultData = [{
  id: 0,
  title: "Spaghetti",
  ingredients: "pasta, oil, sauce, parsley, cheese"
  },
  {
  id: 1,
  title: "PB&J",
  ingredients: "PB, J"
  }
];

You can then find recipe objects by their id and and get access to their properties with something like recipe.title. This makes it easier to pass these down as props too

this is very much how I have structred my recipe app, but I have been unable to get the textarea updates for “instructions” to be saved, and right now the issue is that instructions are 3-4 levels down from my RecipeBox which is at the top (recipebox - recipelist -> recipecard -> ingredients )

Pulling my hair out over this, its been 3 hours.

any suggestions?

I can access teh click handlers fine and can toggle between edit & save modes and can properly render my forms or display…but I cannot save the textfield input.

Do you have a link to your code?