Getting one object out of array

Getting one object out of array
0

#1

So, I dont think I have done this before in javascript. I have the two recipe names taco and pizza already showing in a list. If I click on say pizza then I want to display pizza’s name, ingredients, directions etc. If I click on tacos then the taco info should show up. Which I guess I should put eventlisteners on those li’s so I know they are clicked? This is more just learning and messing around before I attempt the actual project. im assuming I need the index of the array and compare that?

heres the code ive been messing with

class RecipeBox extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      editRecipe: '',
      addRecipe: '',
     recipes :
      [{
        name: "Tacos",
        directions: "make tacos",
        ingredients: ["meat"]
      },
      {
        name: "pizza",
        directions: "bake",
        ingredients: ["dough"]
      }]
    };
    
  
    this.handleAddSubmit = this.handleAddSubmit(this);
    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  
  }///
  
  handleChange (evt) {
   
    this.setState({ [evt.target.name]: evt.target.value }.split(" "));
    
  }
  
  //submit for EditList
   handleSubmit() {
    const recipes = [...this.state.recipes];
    recipes[0].name = this.state.editRecipe;
    this.setState({
      recipes,
      editRecipe: ""
    });
  }
  
  handleAddSubmit(){
    const obj = {'name' : this.state.addRecipe};
    const recipes = [...this.state.recipes]
    recipes.push(obj);
    this.setState({
      recipes,
      addRecipe: ""
    })
    console.log(recipes);
  }
  
  render(){
   
    const ITEMS = this.state.recipes.map(({name}) => <li>{name}</li>)
                                         
    return(
      
      <div>
        
          <ul>                                                     
          {ITEMS}
          </ul>
                                          
          
          <EditList input = {this.state.editRecipe}
          handleChange = {this.handleChange}
          onSubmit = {this.handleSubmit}/>
                                           
          <AddRecipe input = {this.state.addRecipe}
          handleChange = {this.handleChange}
          onAddSubmit = {this.handleAddSubmit} />
        </div>
    )
  }
}

class EditList extends React.Component {
  constructor(props) {
    super(props);
  }
  render() {
     
    return(
      <div>
        <input type='text'
          value = {this.props.input}
          onChange = {this.props.handleChange}
          name="editRecipe"
         />
        <button onClick={this.props.onSubmit}>Edit</button>
       </div>
     )
  }
}

class AddRecipe extends React.Component {
  constructor(props) {
    super(props);
  }
  render() {
     
    return(
      <div>
        <input type='text'
          value = {this.props.input}
          onChange = {this.props.handleChange}
          name="addRecipe"
         />
        <button onClick={this.props.onAddSubmit}>Save</button>
       </div>
     )
  }
}
ReactDOM.render(<RecipeBox />, document.getElementById('app'));

#2

Yes you need to add an event listener to reach li.

What you can do is create a method that sets the current recipe.

handleRecipeClick(index){
  this.setState({
  selectedRecipe: { ...this.state.recipes[index]}    // here you set a copy of the recipe as the selected recipe
});
}

Then you set this function in the event listener on each li

const ITEMS = this.state.recipes.map(({ name }, index) => 
  <li onClick={() => this.handleRecipeClick(index)> {name}</li>)

You can then render you selected recipe based on the state:

<ul>                                                     
          {ITEMS}
</ul>

<h2> Current Recipe </h2>
Name: {this.state.selectedRecipe.name}
Directions: {this.state.selectedRecipe.directions}
Ingredients: {this.state.selectedRecipe.ingredients.join(,)}

Another option is to give each recipe a unique id and then just store the id in the state as selectedId and render it using this.state.recipes.find(recipe => receipe.id === this.state.selecedId)


#4

Sorry it took me so long to reply… took a break yesterday from all this. So I tried your code on codepen just to mess with it, and actually see what its doing. However I cant seem to get it to render, and theres no error in the code pen. I assumed that selectedRecipe was an empty object in state since it gets set to a recipe in the function

class RecipeBox extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
     selectedRecipe: {},
     recipes :
      [{
        name: "Tacos",
        directions: "make tacos",
        ingredients: ["meat"]
      },
      {
        name: "pizza",
        directions: "bake",
        ingredients: ["dough"]
      }]
    };
    this.handleRecipeClick = this.handleRecipeClick.bind(this);
  }

  handleRecipeClick(index){
  this.setState({
  selectedRecipe: { ...this.state.recipes[index]}    // here you set a copy of the recipe as the selected recipe
});
}



 render() {
   const ITEMS = this.state.recipes.map(({ name }, index) => 
  <li onClick={ this.handleRecipeClick(index)}> {name}</li>)
                                            
    return(
      <div>
         <ul> {ITEMS}</ul>
      </div>

#5

The code you posted does not appear to be all of it. If you a Codepen, edit your last post and add a link to the Codepen instead of the partial code.


#6

I do not see in your latest code where you are rendering this.state.selectRecipe.


#7

This is what I have.


#8

If you do not want to add a unique id to each recipe object as suggested by @collinstommy, then your handleRecipeClick and render methods could work something like:


  handleRecipeClick({ target: { id } }) {
    id = Number(id.replace("recipe_ID", ""));
    this.setState({
      selectedRecipe: { ...this.state.recipes[id] } // here you set a copy of the recipe as the selected recipe
    });
  }

  render() {
    const ITEMS = this.state.recipes.map(({ name }, index) => {
      const id = "recipe_ID" + index;
      return (
        <li id={id} key={id} onClick={this.handleRecipeClick}>
          {name}
        </li>
      );
    });

    return (
      <div>
        <AddRecipe
          input={this.state.addRecipe}
          handleChange={this.handleChange}
          onAddSubmit={this.handleAddSubmit}
        />        
        <ul>{ITEMS}</ul>
        {this.state.selectedRecipe.name ? (
          <div>
            <h2>Current Recipe</h2>
            <div>Name: {this.state.selectedRecipe.name}</div>
            <div>Directions: {this.state.selectedRecipe.directions}</div>
            <div>Ingredients: {this.state.selectedRecipe.ingredients.join(",")}</div>
          </div>
        ) : null}
      </div>
    );
  }

However, if you added a unique id to each recipe (like @collinstommy 2nd suggestion) like:

      recipes: [
        {
          id: "recipe1",
          name: "Tacos",
          directions: "make tacos",
          ingredients: ["meat", "tortilla shells", "cheese", "sour cream"]
        },
        { 
          id: "recipe2",
          name: "pizza",
          directions: "bake",
          ingredients: ["dough", "pepporoni", "cheese", "sauce"]
        }
      ]

them the same methods could look like:

  render() {
    const ITEMS = this.state.recipes.map(({ id, name }) => {
      return (
        <li id={id} key={id} onClick={this.handleRecipeClick}>
          {name}
        </li>
      );
    });
    
    const selectedRecipe = this.state.recipes.find(({id}) => id == this.state.selectedRecipeID)
    return (
      <div>
        <AddRecipe
          input={this.state.addRecipe}
          handleChange={this.handleChange}
          onAddSubmit={this.handleAddSubmit}
        />        
        <ul>{ITEMS}</ul>
        {
          selectedRecipe ? (
          <div>
            <h2>Current Recipe</h2>
            <div>Name: {selectedRecipe.name}</div>
            <div>Directions: {selectedRecipe.directions}</div>
            <div>Ingredients: {selectedRecipe.ingredients.join(",")}</div>
          </div>
        ) : null}
      </div>
    );
  }

#9

So, I am going to be 100% honest here. I am not to sure if I understand all that, and I dont want to use code that I do not understand if I was to be asked about it. I liked collin’s original suggestion, but after checking the code pen console im getting a object error somewhere. I do appreciate your willingness to help with those solutions


#10

That is fine. The code posted by @collinstommy for his first suggestion had a typo.

It was missing a closing } before > of the opening li tag. See below for the correct code.

const ITEMS = this.state.recipes.map(({ name }, index) => 
      <li onClick={() => this.handleRecipeClick(index)}> {name}</li>)

#11

I caught that earlier, but I still have an error in the console for code pen. It is

[object error] {
framesToPop:1
name: "invariant violation"
}

I did some looking, and I think it may be the calling of state below the h2? The rest of my code worked before the addition of the new map, and the new rendering


#12

Nevermind! made my own mistake not pay attention to what I was doing. Appreciate the suggestions, and sticking with me