🛠️ Dota Heroes: Refactoring

Hey ,

I'm thrilled to help you learn JavaScript. Unfortunately, you've landed on a page where you cannot access with your current purchase.

Please upgrade (use this link) access this content.

I'm super eager to help you learn more!

🛠️ Dota Heroes: Refactoring

We have finished building the heroes list. Let’s clean up the code before we move on. As before, we’ll look through the code together from top to bottom.

Adding heroes to the DOM

We used this block of code twice to create heroes and add them to the DOM.

const li = document.createElement('li')
li.classList.add('hero')
li.innerHTML = `
  <a href="#">
    <span class="hero__name"> ${hero.localized_name} </span>
    <img src="https://api.opendota.com${hero.img}" alt="${hero.localized_name} image">
  </a>
`
heroesList.appendChild(li)

We can put it into a function called addHeroToDOM.

const addHeroToDom = hero => {
  const li = document.createElement('li')
  li.classList.add('hero')
  li.innerHTML = `
    <a href="#">
      <span class="hero__name"> ${hero.localized_name} </span>
      <img src="https://api.opendota.com${hero.img}" alt="${hero.localized_name} image">
    </a>
  `
  heroesList.appendChild(li)
}

Using addHeroToDOM:

zlFetch(/*...*/)
  .then(response => {
    // ...
    heroes.forEach(addHeroToDOM)

    filterDiv.addEventListener('change', event => {
      // ...
      filtered.forEach(addHeroToDOM)
    })
  })

Filtering Heroes

Most of the code we wrote are used to filter heroes. We can put them into a function so its easier for us to read and understand.

Let’s call this function filterHeroesByCategories

const filterHeroesByCategories = heroes => {
  const selectedAttackTypes = [...document.querySelectorAll('#attack-type input:checked')]
    .map(checkbox => checkbox.id)
  const selectedPrimaryAttributes = [...document.querySelectorAll('#primary-attribute input:checked')]
    .map(checkbox => checkbox.id)
  const selectedRoles = [...document.querySelectorAll('#role input:checked')]
    .map(checkbox => checkbox.id)

  return heroes
    // Filter by attack type
    .filter(hero => {
      if (selectedAttackTypes.length === 0) return true
      const attackType = hero.attack_type.toLowerCase()
      return selectedAttackTypes.includes(attackType)
    })
    // Filter by primary attribute
    .filter(hero => {
      if (selectedPrimaryAttributes.length === 0) return true
      return selectedPrimaryAttributes.includes(hero.primary_attr)
    })
    // Filter by role
    .filter(hero => {
      if (selectedRoles.length === 0) return true
      const heroRoles = hero.roles.map(role => role.toLowerCase())
      return selectedRoles.some(role => {
        return heroRoles.includes(role)
      })
    })
}

Using filterHeroesByCategories:

filtersDiv.addEventListener('change', event => {
  const filtered = filterHeroesByCategories(heroes)
})

Formatting data

We can format the data from the server so it’s easier for us to use later.

For example, we need to use hero.localized_name to get the hero’s name. But we don’t need to remember localized_name when we actually create the hero! We can make it easier for us by changing localized_name to name instead.

To make this change, we’ll run the response through a map function.

zlFetch(`${dotaApi}/constants/heroes`)
  .then(response => {
    const heroes = Object.values(response.body).map(hero => {
      return {
        name: hero.localized_name,
        attackType: hero.attack_type.toLowerCase(),
        primaryAttribute: hero.primary_attr,
        roles: hero.roles
        image: `https://api.opendota.com${hero.img}`
      }
    })

We can do more than changing the property name. If you looked at filterHeroesByCategories, you’ll notice we had to make attack_type and roles lowercase.

We can lowercase these values in this data-formatting phase so we don’t need to do that again later.

zlFetch(`${dotaApi}/constants/heroes`)
  .then(response => {
    const heroes = Object.values(response.body).map(hero => {
	    return {
	      name: hero.localized_name,
	      attackType: hero.attack_type.toLowerCase(),
	      primaryAttribute: hero.primary_attr,
	      roles: hero.roles.map(role => role.toLowerCase()),
	      image: hero.img
	    }
		})
  })
}

We can also add the domain https://api.opendota.com to the image property in this phase, so we don’t need to remember the domain when creating the <li> element!

zlFetch(`${dotaApi}/constants/heroes`)
  .then(response => {
    const heroes = Object.values(response.body).map(hero => {
			return {
			  name: hero.localized_name,
			  attackType: hero.attack_type.toLowerCase(),
			  primaryAttribute: hero.primary_attr,
			  roles: hero.roles.map(role => role.toLowerCase()),
			  image: `https://api.opendota.com${hero.img}`
			}
		})
  })
}

I’ll leave you to adjust the your code to work with the formatted heroes object :)