I’m currently going through Jonas Schmedtmann’s course and ran into a problem. When I search something in a search bar (eg: pizza) it returns the results but when I click on one of the results, it reloads the page instead of displaying it. There is no error in the console either.

Here is the code:

model.js

import { async } from "regenerator-runtime";
import { API_URL } from "./config.js";
import { getJSON } from "./helpers.js";

export const state = {
    recipe: {},
    search: {
      query: '',
      results: [],
    }
};

export const loadRecipe = async function (id) {
   try {
    
    const data = await getJSON(`${API_URL}${id}`);
      
    
      const {recipe} = data.data;
       state.recipe = {
        id: recipe.id,
        title: recipe.title,
        publisher: recipe.publisher,
        sourceUrl: recipe.source_url,
        image: recipe.image_url,
        servings: recipe.servings,
        cookingTime: recipe.cooking_time,
        ingredients: recipe.ingredients,
      }
    console.log(state.recipe);
   
} catch(err) {
    console.log(`${err}`);
    throw err;
}


    
}

export const loadSearchResults = async function(query) {
  try {
    const data = await getJSON(`${API_URL}?search=${query}`);
    console.log(data);

    state.search.results = data.data.recipes.map(rec => {
      return {
        id: rec.id,
        title: rec.title,
        publisher: rec.publisher,
        image: rec.image_url,
        
      }
    })
  } catch(err) {
    console.log(`${err}`);
    throw err;
  }
}


controller.js

import * as model from './model.js';
import recipeView from './views/recipeView.js';
import searchView from './views/searchView.js';
import resultView from './views/resultView.js';


import 'core-js/stable';
import 'regenerator-runtime/runtime'
import { async } from 'regenerator-runtime';

const controlRecipes = async function() {
  try {
  const id = window.location.hash.slice(1);

  if(!id) return;

  recipeView.renderSpinner();

    //Loading Recipes
   await model.loadRecipe(id);
  

  //Rendering 
  recipeView.render(model.state.recipe);

  
  } catch(err) {
    recipeView.renderError();
  }
}

const controlSearchResults = async function() {
  try {
   resultView.renderSpinner();

    //Get search query
   const query = searchView.getQuery();
   if(!query) return;
   
   //Load search results
   await model.loadSearchResults(query);

   //Render results
   
   resultView.render(model.state.search.results);
  } catch(err) {
     console.log(err);
  }
}

controlSearchResults();


const innit = function() {
  recipeView.addHandlerRender(controlRecipes);
  searchView.addHandlerSearch(controlSearchResults);
}
 
innit()








recipeView.js

import VIew from './View';

import icons from  '../../img/icons.svg';
import {Fraction} from 'fractional';

class RecipeView extends VIew {
    _parentElement = document.querySelector('.recipe');
    
    _errorMessage = `We could not find that recipe. Please try another one.`;
    _message = '';

    addHandlerRender (handler) {
      window.addEventListener('hashchange',handler);
      window.addEventListener('load',handler);
    }
    
    _generateMarkup() {
        return  `
  <figure class="recipe__fig">
          <img src="${this._data.image}" alt="Tomato" class="recipe__img" />
          <h1 class="recipe__title">
            <span>${this._data.title}</span>
          </h1>
        </figure>

        <div class="recipe__details">
          <div class="recipe__info">
            <svg class="recipe__info-icon">
              <use href="${icons}#icon-clock"></use>
            </svg>
            <span class="recipe__info-data recipe__info-data--minutes">${this._data.cookingTime}</span>
            <span class="recipe__info-text">minutes</span>
          </div>
          <div class="recipe__info">
            <svg class="recipe__info-icon">
              <use href="${icons}#icon-users"></use>
            </svg>
            <span class="recipe__info-data recipe__info-data--people">${this._data.servings}</span>
            <span class="recipe__info-text">servings</span>

            <div class="recipe__info-buttons">
              <button class="btn--tiny btn--increase-servings">
                <svg>
                  <use href="${icons}#icon-minus-circle"></use>
                </svg>
              </button>
              <button class="btn--tiny btn--increase-servings">
                <svg>
                  <use href="${icons}#icon-plus-circle"></use>
                </svg>
              </button>
            </div>
          </div>

          <div class="recipe__user-generated">
            <svg>
              <use href="${icons}#icon-user"></use>
            </svg>
          </div>
          <button class="btn--round">
            <svg class="">
              <use href="${icons}#icon-bookmark-fill"></use>
            </svg>
          </button>
        </div>

        <div class="recipe__ingredients">
          <h2 class="heading--2">Recipe ingredients</h2>
          <ul class="recipe__ingredient-list">
               ${this._data.ingredients.map(this._generateMarkUpIngredient).join('')}
                
                

           
              </div>
            </li>
          </ul>
        </div>

        <div class="recipe__directions">
          <h2 class="heading--2">How to cook it</h2>
          <p class="recipe__directions-text">
            This recipe was carefully designed and tested by
            <span class="recipe__publisher">${this._data.publisher}</span>. Please check out
            directions at their website.
          </p>
          <a
            class="btn--small recipe__btn"
            href="${this._data.sourceUrl}"
            target="_blank"
          >
            <span>Directions</span>
            <svg class="search__icon">
              <use href="src/img/icons.svg#icon-arrow-right"></use>
            </svg>
          </a>
        </div>
  `;
  
    }

   _generateMarkUpIngredient(ing) {

    return `
    <li class="recipe__ingredient">
  <svg class="recipe__icon">
    <use href="src/img/icons.svg#icon-check"></use>
  </svg>
  <div class="recipe__quantity">${ing.quantity ? new Fraction(ing.quantity).toString() : ""}</div>
  <div class="recipe__description">
    <span class="recipe__unit">${ing.unit}</span>
    ${ing.description}
  </div>
</li> `;
}
}
export default new RecipeView();

View.js

import icons from  '../../img/icons.svg';

export default class VIew {
    _data;
    render(data) {
        this._data = data;
        const markup = this._generateMarkup();
        this._clear();
        this._parentElement.insertAdjacentHTML('afterbegin', markup)
    }

    _clear() {
        this._parentElement.innerHTML = '';
    }

    renderSpinner() {
        const markup = `
        <div class="spinner">
                <svg>
                  <use href="${icons}#icon-loader"></use>
                </svg>
              </div>
        `
       this._parentElement.innerHTML = '';
       this._parentElement.insertAdjacentHTML('afterbegin', markup)
      }

    
    
    renderError(message = this._errorMessage) {
      const markup = `
      <div class="error">
      <div>
        <svg>
          <use href="${icons}#icon-alert-triangle"></use>
        </svg>
      </div>
      <p>${message}</p>
    </div>
      `

     this._clear();
     this._parentElement.insertAdjacentHTML('afterbegin',markup) 
    }

    renderMessage(message = this._message) {
      const markup = `
      <div class="message">
      <div>
        <svg>
          <use href="${icons}#icon-smile"></use>
        </svg>
      </div>
      <p>${message}</p>
    </div>
      `

     this._clear();
     this._parentElement.insertAdjacentHTML('afterbegin',markup) 
    }

}

resultView.js

import icons from  '../../img/icons.svg';
import View from "./View";

class ResultView extends View {
 _parentElement = document.querySelector('.results');

 _generateMarkup() {
   return this._data.map(this._generateMarkupPreview).join('');

    
 }

 _generateMarkupPreview(result) {
    return `
    <li class="preview">
            <a class="preview__link " href="${result.id}">
              <figure class="preview__fig">
                <img src="${result.image}" alt="${result.title}" />
              </figure>
              <div class="preview__data">
                <h4 class="preview__title">${result.title}</h4>
                <p class="preview__publisher">${result.publisher}</p>
                
              </div>
            </a>
          </li>
    `
 }
}

export default new ResultView();

searchView.js

class SearchView {
  _parentEL = document.querySelector('.search');

  getQuery() {
   const query = this._parentEL.querySelector('.search__field').value;
   this._clearInput();
   return query;
  }

  _clearInput() {
    this._parentEL.querySelector('.search__field').value = '';
  }

  addHandlerSearch(handler) {
    this._parentEL.addEventListener('submit', function(e) {
        e.preventDefault();

        handler();
    })
  }
}

export default new SearchView();

helpers.js

import { TIMEOUT_SECS } from "./config.js";

const timeout = function (s) {
    return new Promise(function (_, reject) {
      setTimeout(function () {
        reject(new Error(`Request took too long! Timeout after ${s} second`));
      }, s * 1000);
    });
  };

export const getJSON = async function(url) {
    try {
        const res = await Promise.race([fetch(url), timeout(TIMEOUT_SECS)]);
       
        const data = await res.json();  
      
    
        if(!res.ok) throw new Error(`${data.message} (${res.status})`) ;
        return data;
    } catch(err) {
       throw err;
    }
}


config.js

export const API_URL = 'https://forkify-api.herokuapp.com/api/v2/recipes/';
export const TIMEOUT_SECS = 10;

New contributor

TAL is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.

1