Getting one object out of array

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'));

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)

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>

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.

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

This is what I have.

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>
    );
  }

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

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>)

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

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

1 Like