3

I'm making a RecipeBox App in React, it works fine until I try to implement a search functionality. The error is thrown as soon as the user enters a search item in the search bar. The else block in this render method is what throws the error -

render() {
        var gridRecipes = this.props.recipes;
        let onDelete = this.props.onRecipeDelete;


        //second set of props
        let onRecipeNameEdit = this.props.onRecipeNameEdit;
        let onRecipeIngEdit = this.props.onRecipeIngredientsEdit;
        let onRecipeTagsEdit = this.props.onRecipeTagsEdit;
        let onRecipeEditSubmit = this.props.onRecipeEditSubmit;
        let onRecipeEditSelect = this.props.onRecipeEditSelect;

        let filterNameEdit = this.props.filterNameEdit;
        let filterIngredientsEdit = this.props.filterIngredientsEdit;
        let filterTagsEdit = this.props.filterTagsEdit;

        //props for search
        let searchedItem = this.props.searchItem;


        console.log(gridRecipes);
        var recipeCards = [];

        if(searchedItem === "") {
            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>
            });
        }

        else {
            console.log(`user searched for ${searchedItem}`);
            recipeCards = gridRecipes.filter(function (recipe, index) {
                //console.log(recipe);
                if(recipe.name.toLowerCase().indexOf(searchedItem) !== -1) {
                    return <div className="recipe-item" key={index}>
                        <RecipeCard recipe={recipe} key={index} index={index} onDelete={onDelete}
                                    filterNameEdit={filterNameEdit}
                                    filterIngredientsEdit={filterIngredientsEdit}
                                    filterTagsEdit={filterTagsEdit}
                        />
                    </div>
                }
            })
            console.log(recipeCards);
            console.log(Array.isArray(recipeCards));
        }

        return (
            <div className="flex-container">
                {recipeCards}
            </div>
        )
    }

Here's the error message -

Uncaught Error: Objects are not valid as a React child (found: object with keys {name, ingredients, tags}). If you meant to render a collection of children, use an array instead or wrap the object using createFragment(object) from the React add-ons. Check the render method of `RecipeFlexContainer`.

enter image description here Looking at the error message and reading another similar question, it appears that the collection of children is being rendered as an individual object/collection of objects, as opposed to an array containing the objects. But that is not the case. I've got two console.log statements in the code to verify this -

console.log(recipeCards);
console.log(Array.isArray(recipeCards));

The console shows -

[Object]
true

Which shows that recipeCards is a an array containing object(s). But then the return throws off the error -

return (
            <div className="flex-container">
                {recipeCards}
            </div>
        )

Interestingly, the same return statement works fine, without throwing any errors, if I remove the search bar functionality and delete the else block entirely -

else {
            console.log(`user searched for ${searchedItem}`);
            recipeCards = gridRecipes.filter(function (recipe, index) {
                //console.log(recipe);
                if(recipe.name.toLowerCase().indexOf(searchedItem) !== -1) {
                    return <div className="recipe-item" key={index}>
                        <RecipeCard recipe={recipe} key={index} index={index} onDelete={onDelete}
                                    filterNameEdit={filterNameEdit}
                                    filterIngredientsEdit={filterIngredientsEdit}
                                    filterTagsEdit={filterTagsEdit}
                        />
                    </div>
                }
            })
            console.log(recipeCards);
            console.log(Array.isArray(recipeCards));
        }

Here's my app on Codepen.

Steps to reproduce -

  1. Search for an existing recipe name in the search bar, like "garlic"
  2. Pull up the console

This is in the RecipeFlexContainer component.

Can anyone explain what's going on?

Community
  • 1
  • 1
Manish Giri
  • 3,562
  • 8
  • 45
  • 81
  • so what you want to accomplish is to filter the recipes when the user starts typing the recipe name? – bntzio Apr 27 '17 at 00:12
  • Yes, precisely. Sorry if that wasn't clear. I want to display recipes only if their names have what the user enters. – Manish Giri Apr 27 '17 at 00:14
  • Sam just answered your question, the thing is, you want to return the recipe and save it into the `recipeCards` variable, that's why `map` is used, instead of `filter`. – bntzio Apr 27 '17 at 00:21

1 Answers1

3

Change the filter to map and you'll have it fixed:

// after
recipeCards = gridRecipes.map(function (recipe, index) {

// before
recipeCards = gridRecipes.filter(function (recipe, index) {

But a cleaner way would be:

gridRecipes
  .filter(r => r.name.toLowerCase().indexOf(searchedItem) !== -1)
  .map((r, i) => (
    <div className="recipe-item" key={i}> 
      <RecipeCard /> 
    </div>)
  );
Sam R.
  • 16,027
  • 12
  • 69
  • 122
  • @bntzio @sam-rad Got it! Thank You! Although, I didn't understand why using `.filter()` directly didn't work. I thought just like `.map()`, `.filter()` would also return an array with the elements (objects, in this case) which pass the test `r.name.toLowerCase().indexOf(searchedItem) !== -1`. – Manish Giri Apr 27 '17 at 00:40