React RecipeBox bug with props

I’m building the RecipeBox App.

I’ve got the following features so far -

  1. Lets you add a new recipe(name + ingredients + tags)
  2. Lets you delete an existing recipe
  3. Lets you edit an existing recipe <- this is not working right now

Here’s my app on Codepen.

The problem I’m facing with the edit recipe feature is that no matter which recipe you edit, in addition to your selected recipe, the app also edits the very first recipe item with the changes you make.

The three main components you’ll need to focus on are - RecipeBoxApp, RecipeFlexContainer and RecipeCard. RecipeBoxApp acts as the outermost container component. RecipeFlexContainer acts as the (flex container) container component for each RecipeCard (which acts as the flex item).

Here’s my RecipeCard component -

class RecipeCard extends React.Component {
    constructor(props) {
        super(props);
        this.handleRecipeDelete = this.handleRecipeDelete.bind(this);
        this.handleRecipeNameChange = this.handleRecipeNameChange.bind(this);
        this.handleRecipeIngredientsChange = this.handleRecipeIngredientsChange.bind(this);
        this.handleRecipeTagsChange = this.handleRecipeTagsChange.bind(this);
        this.handleEditRecipeSubmit = this.handleEditRecipeSubmit.bind(this);
        this.handleRecipeEditSelection = this.handleRecipeEditSelection.bind(this);

        //test key of index
        //this.cardKey = this.props.index;


    }

    handleRecipeNameChange(e) {
        this.props.onRecNameEdit(e.target.value);

    }

    handleRecipeIngredientsChange(e) {
        this.props.onRecIngredientsEdit(e.target.value);
    }

    handleRecipeTagsChange(e) {
        this.props.onRecTagsEdit(e.target.value);
    }

    handleRecipeEditSelection() {
        console.log(this.props.index);
        this.props.onRecipeEdit(this.props.index);
    }


    handleEditRecipeSubmit(e) {

        console.log(this.props.index);

        let pos = this.props.index;
        //let pos = this.cardKey;
        console.log(`Recipe to be edited = #${pos}`);
        this.props.onRecEditSubmit(pos);

    }

    handleRecipeDelete(e) {
        //console.log(e.target.value);
        console.log(this.props.index);
        //call onDelete event handler with number of recipecard to be deleted
        this.props.onDelete(this.props.index);
    }

    render() {
        var recipeName = this.props.recipe.name;
        var ingredients = this.props.recipe.ingredients.split(",").map(function (ingredient, index) {
            return <a href="#!" className="collection-item" key={index}>{ingredient}</a>
        });
        var tags = this.props.recipe.tags.split(",").map(function (tag, index) {
            return  <div className="chip" key={index}>{tag}<i className="close material-icons">close</i></div>
        });

        return (
            <div>
                <div className="card medium hoverable">
                    <div className="card-image waves-effect waves-block waves-light">
                        <img className="activator" src="https://search.chow.com/thumbnail/320/0/www.chowstatic.com/s/recipe_placeholder_main_img-710dbe7756144f55f01c42cc4892e6de.jpg"/>
                    </div>
                    <div className="card-content">
                        <span className="card-title activator grey-text text-darken-2" onClick={this.handleRecipeEditSelection}><i className="material-icons brown-text text-darken-4 right">more_vert</i></span>
                        <h1 style={styles.cardContent.title}>{recipeName}</h1>
                        {/*<p><a href="#">This is a link</a></p>*/}
                        <div id="tags" style={styles.cardContent.tags}>
                            {tags}
                        </div>
                        <a className="btn-floating pulse waves-effect waves-light red right" style={styles.cardContent.deleteButton} onClick={this.handleRecipeDelete}><i className="material-icons right">delete</i></a>
                    </div>
                    <div className="card-reveal">
                        <span className="card-title grey-text text-darken-4"><i className="material-icons right">close</i></span>
                        <h4 style={styles.cardReveal.header}>{recipeName}</h4>
                        {/*<h5>Ingredients</h5>*/}
                        <div className="collection">
                            {ingredients}
                        </div>
                        <a className="btn-floating btn-large waves-effect waves-light cyan darken-4 right pulse" href="#modal2"><i className="material-icons right">mode_edit</i></a>
                    </div>
                </div>
                <div id="modal2" className="modal">
                    <div className="modal-content">
                        <h2 style={styles.modal.header}>Edit Recipe</h2>
                        <div className="row">
                            <form className="col s12">
                                <div className="row">
                                    <div className="input-field col s6">
                                        <input id="input_text2" type="text" data-length="10" value={this.props.filterNameEdit} onChange={this.handleRecipeNameChange}/>
                                        <label htmlFor="input_text2">Name</label>
                                    </div>
                                </div>
                                <div className="row">
                                    <div className="input-field col s12">
                                        <textarea id="textarea3" className="materialize-textarea" data-length="120" value={this.props.filterIngredientsEdit} onChange={this.handleRecipeIngredientsChange}></textarea>
                                        <label htmlFor="textarea3">Ingredients (separate with commas)</label>
                                    </div>
                                </div>
                                <div className="row">
                                    <div className="input-field col s12">
                                        <textarea id="textarea4" className="materialize-textarea" data-length="30" value={this.props.filterTagsEdit} onChange={this.handleRecipeTagsChange}></textarea>
                                        <label htmlFor="textarea4">Tags (separate with commas)</label>
                                    </div>
                                </div>
                            </form>
                        </div>
                    </div>
                    <div className="modal-footer">
                        <a href="#!" className="modal-action modal-close waves-effect waves-green btn btn-floating btn-large green darken-4 pulse" onClick={this.handleEditRecipeSubmit}><i className="material-icons">done</i></a>
                    </div>
                </div>
            </div>
        )
    }
}

The faulty function is this one -

handleEditRecipeSubmit(e) {
    
            console.log(this.props.index);
    
            let pos = this.props.index;
            //let pos = this.cardKey;
            console.log(`Recipe to be edited = #${pos}`);
            this.props.onRecEditSubmit(pos);
    
        }

This works as an event handler whenever you selected a recipe for edit and click the “done” button for saving the changes. The problem is that this.props.index always returns 0, no matter which recipe is being edited. index is the prop passed in to RecipeCard and it contains the sequence of each recipecard when it was being created, using .map(), in component RecipeFlexContainer -

var recipeCards = gridRecipes.map(function (recipe, index) {
            return <div className="recipe-item" key={index}>
                <RecipeCard recipe={recipe} key={index} index={index} onDelete={onDelete}
                            onRecNameEdit={onRecipeNameEdit}
                            onRecIngredientsEdit={onRecipeIngEdit}
                            onRecTagsEdit={onRecipeTagsEdit}
                            onRecEditSubmit={onRecipeEditSubmit}
                            filterNameEdit={filterNameEdit}
                            filterIngredientsEdit={filterIngredientsEdit}
                            filterTagsEdit={filterTagsEdit}
                            onRecipeEdit={onRecipeEditSelect}
                />
            </div>
        });

What I don’t understand is why this.props.index always returns 0 here in the handleEditRecipeSubmit function when you try to edit a recipe, while it correctly returns the sequence of the card if you try to delete it instead-

//this works perfectly
    handleRecipeDelete(e) {
        //console.log(e.target.value);
        console.log(this.props.index);
        //call onDelete event handler with number of recipecard to be deleted
        this.props.onDelete(this.props.index);
    }

I’m mostly building this app locally, that version is here. SearchBar is yet to be hooked up.

Steps to reproduce -

  1. Click on the three vertical dots on any recipe card (other than the first)
  2. Modify any field in the resulting form
  3. Click the done button
  4. Changes are reflected on the selected card and the first card

Would appreciate any help in finding the bug.

I would say it’s because of jQuery modal.
Try to replace it with something like this: https://github.com/reactjs/react-modal

Yep. Looks like a problem with the modal.
When someone clicks on the link to open “#modal2”, there are more than one modal with an id of modal2 (one for each recipe). So when you submit a change, something is getting mixed up!

react-modal will open depending on the state of the component, so you should be good.

^^^ yeah mixing react and jquery is like mixing beer with liquor

Here is an article on modals in React that I found helpful when I made my recipe box project.