6
\$\begingroup\$

I made a recipe box with ReactJS. Please let me know what room for improvement you see. I did notice that with codepen, the list of components started to become lengthy to look at. Is it a best practice to separate the different components into different js files? Here is the codepen: https://codepen.io/capozzic1/pen/gmpVyr. Here is the code:

var Grid = ReactBootstrap.Grid;
var Col = ReactBootstrap.Col;
var Row = ReactBootstrap.Row;

/*
Component ideas
-recipeCon
  -RecipeList
        -Recipe ingredients 
                        -edit-delete
-addRecipe button 
            -recipe entry

*/

var recipes = [{
        title: "Pizza",
        ingredients: ["Tomato Sauce", "Yeast"]
    }, {
        title: "Cookies",
        ingredients: ["Cookie Dough", "Chocolate chips"]
    }, {
        title: "Turkey tacos",
        ingredients: ["Nacho cheese taco shells", "Turkey"]
    }

]
//holds all other components. has add/edit methods
class RecipeContainer extends React.Component {
    constructor(props) {
        super(props);
        this.state = ({
            recipes: recipes,
            edit: false,
            editTitle: "",
            editIngred: "",

        });
        this.addRecipe = this.addRecipe.bind(this);
        this.editRecipe = this.editRecipe.bind(this);
        this.addBtnEdit = this.addBtnEdit.bind(this);
        this.editText = this.editText.bind(this);
        this.editContents = this.editContents.bind(this);
        this.handleOpen = this.handleOpen.bind(this);
    }

    editRecipe() {
        this.setState({
            edit: true
        });

    }
    editText(editTitle, editIngred) {
        this.setState({
            editTitle: editTitle,
            editIngred: editIngred
        })
    }

    addRecipe(recipe) {
        recipes.push(recipe);
        this.setState({
            recipes: recipes
        });
    }

    addBtnEdit() {
        this.setState({
            edit: false
        });
    }
    editContents() {
        this.setState({
            recipes: recipes
        });
        console.log(recipes);
    }

    handleOpen() {
        document.querySelector(".recEntry").style.visibility = 'visible';
    }

    render() {

        return (
            <Grid>
                <Row className="show-grid">
                    <Col md={12}>

                    <div className="recipeCon">
                        <h2 className="text-center mainh2">A recipe box</h2>
                  <RecipeList recipes={this.state.recipes} 
                                  edit={this.editRecipe} 
                                  editText={this.editText} 
                               deleteContents={this.editContents}  
                               open={this.handleOpen}/>

                  <AddRecipeBtn editTrue={this.addBtnEdit} 
                                 open={this.handleOpen}/>

                </div>
                <div className="recEntryWrap">
            <RecipeEntry addRecipe={this.addRecipe} 
                                editStat={this.state.edit} 
                                editTitle={this.state.editTitle} 
                                editIngred={this.state.editIngred} 
                                editCont={this.editContents}/>
            </div>
                </Col>
                    </Row>
                        </Grid>
                );
    }
}

//show list of recipe names
class RecipeList extends React.Component {
    constructor(props) {
        super(props);
        this.handleEdit = this.handleEdit.bind(this);
        this.handleDelete = this.handleDelete.bind(this);
        this.handleToggle = this.handleToggle.bind(this);
    }
    handleEdit(e) {
        this.props.edit();
        var editTitle = $(e.target).siblings("h2").text();
        var editIngred = $(e.target).siblings("li").map(function(i, listi) {
            return $(listi).text();
        }).toArray().join(',');

        this.props.editText(editTitle, editIngred);
        //document.appendChild(test);
        //value={editStat ? editTitle : ""}
        $('.titleSpan').text(editTitle);
        $('.inpIngred').val(editIngred);
        this.props.open();
    }
    handleDelete(e) {
        var recTitle = $(e.target).siblings("h2").text();

        for (var i = 0; i < recipes.length; i++) {

            if (recTitle === recipes[i].title) {
                recipes.splice(i, 1);
            }
        }
        this.props.deleteContents();
    }

    handleToggle(e) {
            var sib = $(e.target).siblings().toggle();
            console.log(sib);
        }
        //onclick of edit, get title and ingredients
    render() {
    //generate list of li items and add to ul
        var recipes = this.props.recipes;
        var recLi = recipes.map((recipe, index) =>
            <div key={index}>
            <h2 className="rech2" onClick={this.handleToggle}>{recipe.title}</h2>
                {
                recipe.ingredients.map((ingred,index) =><li key={index + "id"}>                 {ingred}</li>)
                }                       
        <button onClick={this.handleEdit} className="editBtn">Edit</button>
        <button onClick={this.handleDelete} className="deleteBtn">Delete</button>
            </div>
        );
        return (
            <div className="recList">
            <ul className="recUl">
        {recLi}
        </ul>
      </div>
        );
    }
}
//recipe button for showing recipe entry div 
class AddRecipeBtn extends React.Component {
    constructor(props) {
        super(props);
        this.showEntry = this.showEntry.bind(this);
    }

    showEntry() {
        this.props.open();
        this.props.editTrue();
        //empty the inputs and spans whenever add new recipe is clicked
        $(".inpIngred").val("");
        $(".inpName").val("");
        $(".titleSpan").text("");
    }
    render() {
        return (
            <button onClick={this.showEntry} className="bigAdd">Add New Recipe</button>
        );
    }

}
//enter a new recipe, also handles edit of recipes 
class RecipeEntry extends React.Component {
    constructor(props) {
        super(props);
        this.handleAdd = this.handleAdd.bind(this);
        this.handleEdit = this.handleEdit.bind(this);
        this.handleClose = this.handleClose.bind(this);
    }
    handleAdd() {
        var titleVal = document.querySelector(".inpName").value;
        var ingVal = document.querySelector(".inpIngred").value;
        var newRecipe;
        var ingArr = ingVal.split(",");

        for (var i = 0; i <= recipes.length; i++) {
            if (recipes.length >= 0) {
                newRecipe = {
                    title: titleVal,
                    ingredients: ingArr
                };
                this.props.addRecipe(newRecipe);
                this.handleClose();
                break;
            } else if (titleVal === recipes[i].title) {
                alert("This recipe already exists");
                break;
            }
        }

    }
    handleClose() {
        var recEntry = document.querySelector(".recEntry");
        recEntry.style.visibility = "hidden";

    }
    handleEdit() {

        var titleVal = document.querySelector(".titleSpan").textContent;
        var ingVal = document.querySelector(".inpIngred").value;

        var ingArr = ingVal.split(",");
        console.log(titleVal);
        //update specific object for recipes array
        for (var i = 0; i < recipes.length; i++) {

            if (recipes[i].title == titleVal) {
                var newTitle = document.querySelector(".inpName").value;
                recipes[i].title = newTitle;
                recipes[i].ingredients = ingArr;

            }
        }
        console.log(recipes);
        //let the recipe container know to update the state
        this.props.editCont();

        this.handleClose();
    }

    render() {
        var editStat = this.props.editStat;
        var editTitle = this.props.editTitle;
        var editIng = this.props.editIngred;

        return (
            <div className="recEntry">

                    <h2>{editStat ? 'Edit a recipe' : 'Add a recipe'}</h2>
                    <h3>Title</h3>
                <p><b>{editStat ? 'Old title: ' :  ""}</b><span className="titleSpan"></span></p>
                    <input type="text" className="inpName" />
                  <h3>Ingredients</h3>
                <p className="instruct">Please put commas(",") in between ingredient names.</p>
                    <textarea className="inpIngred" />
                    <button onClick={editStat ? this.handleEdit : this.handleAdd} className="saveBtn">{editStat ? 'Save' : 'Add recipe'}</button>
                    <button onClick={this.handleClose} className="closeBtn">Close</button>

            </div>
        );
    }
}

ReactDOM.render(<RecipeContainer/>, document.querySelector(".recWrap"));

/*$(".rech2").on('click',function(){
    console.log("test");
});*/
\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

Just a few quick observations:

This method:

editText(editTitle, editIngred) {
  this.setState({
    editTitle: editTitle,
    editIngred: editIngred
  })
}

can be written like this:

editText = (editTitle, editIngred) => this.setState({editTitle, editIngred});

It looks better, it's more clean. Also, you now can use this in your render method like this:

<button onClick={this.editText}>Edit</button>.

No need to bind in the header. A very good article about this can be read here (5. Use Arrow Function in Class Property).


You can also minimize the amount of code when doing

var editStat = this.props.editStat;
var editTitle = this.props.editTitle;
var editIng = this.props.editIngred

You will achieve the same only using

const { editStat, editTitle, editIng} = this.props

Another suggestion I would give you is to use this approach

{ editStat && 'Edit a recipe' || 'Add a recipe' }

instead of

{ editStat ? 'Edit a recipe' : 'Add a recipe' }
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.