roger-melo-treinamentos / curso-de-js-roger-melo

Repositório de informações do CJRM
491 stars 170 forks source link

Aplicação: Quiz Interativo #6127

Closed MateusCavalheiro-prog closed 1 year ago

MateusCavalheiro-prog commented 1 year ago

As cores da sua versão da aplicação são diferentes das cores da aplicação mostrada na aula?

Sim

Sua aplicação contém funcionalidades que não foram mostradas nas aulas da aplicação?

Não

Sua aplicação contém funcionalidades da linguagem que não foram mostradas no CJRM até aqui?

Não

Link do repositório da aplicação (ou pasta pública no Google Drive)

https://github.com/MateusCavalheiro-prog/rock-quiz-cjrm.git

Maiores dificuldades durante a implementação

Menores dificuldades durante a implementação

-obter as referências das tags e valores das respostas do usuário. -usar o listeners de eventos. -iterar pelos array.

obs: meu email de cadastro no curso é: mateuscavalheiro02@gmail.com.

MivlaM commented 1 year ago

Olá @MateusCavalheiro-prog

Passando para dizer que a issue foi visualizada e em até 10 dias úteis a análise será feita =)

MivlaM commented 1 year ago

Olá @MateusCavalheiro-prog

Parabéns pelo esforço em construir a primeira aplicação do treinamento =)

Como foi minha primeira aplicação minha maior dificuldade foi quebrar os problemas em partes e organizar o código.

Mesmo com a dificuldade relatada, você conseguiu!

Essa habilidade de organização é uma habilidade que é elevada com a prática.

Conforme você praticar, você vai percebendo aos poucos o que pode ser melhorado.

Tive dificuldade em nomear as funções e variáveis para que elas fizessem sentido no código.

No geral você escolheu bons nomes para as funções e variáveis, mas de qualquer forma, fica aqui uma sugestão:

Um princípio que gosto de seguir ao nomear é:

Recomendo que você siga esse princípio ao nomear, é uma excelente maneira de deixar cada parte do código bem específica em relação aos nomes.

tive um pouco de dificuldade em fazer aparecer o popup com o resultado do quiz devido as classes do bootstrap, mas acredito que eu tenha conseguido resolver.

Sem problemas, não há certo ou errado na forma de exibir os pontos, o mais importante num primeiro momento você já fez, que é implementar a lógica de exibição. De qualquer forma, uma maneira de implementar os pontos será mostrada nas aulas seguintes.

Quanto à estilização, o uso do Bootstrap (ou qualquer lib CSS) é opcional.

Veja o que fica mais confortável pra você.


Boas decisóes

Você deixou a primeira alternativa de cada pergunta marcada por padrão. Acredito que isso incentiva o usuário a marcar a resposta que ele realmente acredita que seja a verdadeira.

Outra boa decisão que você tomou foi a de deixar o input dentro da label. Isso faz com que o usuário não precise posicionar o mouse e clicar exatamente no input type radio. Basta clicar no texto da alternativa =)

Uma ideia interessante que você teve em relação à usabilidade foi a de posicionar a mensagem dos pontos de forma que ela estivesse bem próxima do centro da tela após clicar no botão. Isso é bom pois a pontuação aparece justamente onde o usuário estará olhando após enviar as respostas.

Você entendeu que não era necessário manipular DOM dentro do forEach (um erro comum que algumas pessoas cometem). A ideia do forEach ali naquele momento era apenas calcular os pontos, exatamente como vc fez =)


Vou deixar abaixo algumas observações sobre sua aplicação


Comentários no código

No index.html há alguns trechos de código comentados, não é uma regra, mas evite comentários.

Comentários podem mentir se o código for modificado e o comentário correspondente não for atualizado, o comentário perde o sentido. Isso sem mencionar o retrabalho de atualizar comentários.


Indentação com quatro espaços ao invés de três

Recomendo que ao invés de 3 espaços para indentação, use 2. E isso vale para qualquer linguagem (HTML, CSS, JS, etc).

Se um dia vc quiser enviar alguma sugestão ou modificação de artigo pro MDN, por exemplo, seu código terá que seguir o code-guideline deles:

E não só o MDN, mas muitas outras fontes recomendam que o código tenha 2 espaços ao invés de 3.

Seu olho irá cansar menos, pq suas linhas de código serão menos extensas, o que impacta na legibilidade e carga cognitiva na leitura.

À partir daqui, os exemplos do seu código que postarei abaixo já estarão formatados com 2 espaços, ao invés de 3.

Para configurar espaços no VSCode, dá uma olhada aqui:

Vc verá que nesse parágrafo, há um link para o get started de Settings do VSCode. Recomendo dar uma lida, mas não invista muito tempo nisso =)


Linhas em branco

Um princípio: linhas em branco indicam, visualmente, a separação entre conceitos. Procure evitar o excesso ou a falta delas no código.

Sabendo disso, você pode remover do início do código, a linha 5 que está em branco.

// antes
const correctAnswers = ['B', 'B', 'B', 'B']
const form = document.querySelector('.quiz-form')
const quizResultPopup = document.querySelector('.wrapper-card')

const getUserAnswers = form => {
  // ...
}
// depois
const correctAnswers = ['B', 'B', 'B', 'B']
const form = document.querySelector('.quiz-form')
const quizResultPopup = document.querySelector('.wrapper-card')

const getUserAnswers = form => {
  // ...
}

Code Styling

Ao longo do código, há algumas inconsistências em relação ao espaço antes e depois das chaves de abertura da arrow function:

// Espaço em excesso após a seta da arrow function
const scoreMessage = score =>  { 
  // ...
}

// Espaço em excesso após o sinal de atribuição da função
const showResultQuiz =  event => {
  // ...
}

// Função declarada sem nenhuma ocorrência de espaços em excesso
const getUserAnswers = form => {
  // ...
}

Uma sugestão é que você escolha uma das formas e seja consistente com essa escolha no decorrer do código. Por convenção, recomendo que evite deixar esses espaços em branco e prefira declarar uma função como a última mostrada no bloco acima.

Futuramente, você pode usar uma ferramenta automatizada de formatação de código, mas nesse momento da sua jornada, eu recomendo que vc formate manualmente.

Isso vai "forçar" vc a adotar um estilo de escrita de código e ser consistente com esse estilo ao implementar suas aplicações.


Refatoração de getUserAnswers

Feito esses ajustes, nessa observação e nas seguintes, vou te mostrar duas abordagens de refactoring usando métodos que você vai encontrar em breve nas aulas seguintes, ambos serão explicados com detalhes futuramente. O que quero que você entenda, nesse primeiro momento, é o conceito por trás deles. Sabendo disso, observe o código abaixo:

Podemos fazer uma abordagem interessante aqui:

const getUserAnswers = form => {
  return [
    form.inputQuestion1.value,
    form.inputQuestion2.value,
    form.inputQuestion3.value,
    form.inputQuestion4.value
  ]
}

Você percebe todas essas repetições de form.inputQuestion.value? Pois então, vamos fazer uma refatoração nessa função para que ela possa obter as respostas dos inputs do usuário sem precisar de todas essas repetições. Para isso, vamos manter apenas o nome getUserAnswers, depois disso, nela podemos criar uma const userAnswer que vai receber uma expressão que pega o valor de cada inputQuestion do form. userAnswers vai receber um array vazio que será preenchido após percorrermos cada item dentro do array correctAnswers por meio do método forEach. Ficaria assim:

const getUserAnswers = () => {
  const userAnswers = []

  correctAnswers.forEach((_, index) => {
    const userAnswer = form[`inputQuestion${index + 1}`].value
    userAnswers.push(userAnswer)
  })

  return userAnswers
}

Tudo certo até aqui? Se sim, vamos prosseguir! A ideia do código acima é a seguinte, baseado em um array, você quer obter um novo array que contém a mesma quantidade de itens do array original. Por quê? correctAnswers tem quatro itens e userAnswers quando está sendo retornado também vai ter quatro itens. Lembre-se da ideia de que quando baseado em um array, você precisa obter um novo array com a mesma quantidade de itens do array original, você pode usar um método chamado map. Sabendo disso, ao invés de userAnswers ser declarada como um array vazio, o userAnswers pode receber o retorno dessa expressão. Feito isso, ao invés de executar o método push, é necessário apenas retornar userAnswers. Veja abaixo:

const getUserAnswers = () => {
  const userAnswers = correctAnswers.map((_, index) => {
    const userAnswer = form[`inputQuestion${index + 1}`].value
    return userAnswer
  })

  return userAnswers
}

Feito isso, você pode refatorar a função ainda mais ao usar os retornos implícitos das arrow functions, removendo os blocos e retornos:

const getUserAnswers = () => correctAnswers.map((_, index) => form[`inputQuestion${index + 1}`].value)

Fazendo isso, o map vai ser executado para cada item de correctAnswers, o callback está retornando o valor da resposta do usuário, esse valor que o callback está retornando vai ser inserido num novo array que o map está gerando por baixo dos panos. A cada execução desse callback, o valor que a função está retornando vai ser inserido nesse novo array que o map está gerando por baixo dos panos. Quando esse callback for executado para todos os itens, o map vai retornar esse novo array que ele gerou e toda a expressão vai resultar no array que o map retornou.

Por fim, a getUserAnswers já vai estar funcionando normalmente e você pode manter a const userAnswers armazenando a invocação dela, porém não será mais necessário passar o form como argumento dessa função em showResultQuiz.

const showResultQuiz = event => {
  event.preventDefault()

  const userAnswers = getUserAnswers()
   // ...
}

scoreMessage

Há um padrão que você estabeleceu nesse trecho do código que é o da porcentagem, é uma boa prática inserir uma porcentagem na mensagem dos pontos. Isso é bom pq traz mais clareza do progresso do usuário no quiz em relação a pontuação máxima. Você pode fazer o mesmo para caso o usuário não acerte nenhuma alternativa, a vantagem disso, além dos benefícios citados acima, é manter o padrão estabelecido com as outras pontuações:

// antes
const scoreMessage = score => { 
  return {
    0: 'Mais sorte da proxima vez! você não acertou nenhuma =(',
    25: 'Parabéns você acertou 25%!',
    50: 'Parabéns você acertou 50%!',
    75: 'Parabéns você acertou 75%!',
    100: 'Exelente você acertou 100%!',
  }[score]
}
// depois
const scoreMessage = score => { 
  return {
    0: 'Mais sorte da proxima vez! você não acertou nenhuma, 0% =(',
    25: 'Parabéns você acertou 25%!',
    50: 'Parabéns você acertou 50%!',
    75: 'Parabéns você acertou 75%!',
    100: 'Exelente você acertou 100%!',
  }[score]
}

showResultQuiz

Se fizer sentido para você, no intuito de melhorar a legibilidade do código, você também pode renomear a função showResultQuiz para algo como showScore. Dessa forma, você reduz a quantidade de caracteres, além de que ao bater o olho no nome da função, a ação que ela executa fica mais explícita. Dessa forma, a leitura e escrita do código se torna mais fácil. Ficaria assim:

// antes
const showResultQuiz = event => {
  // ...
}
// depois
const showScore = event => {
  // ...
}

Para a segunda abordagem, quero que observe o código abaixo:

userAnswers.forEach((useranswer, index) => {
  const correctAnswer = useranswer === correctAnswers[index]

  if (correctAnswer) {
    score += 25
  }
})

Toda a ideia do código acima é a seguinte: baseado em um array, você quer gerar um número, isto é, um número que seja a pontuação. E quando baseado num array você quer gerar um número, você pode fazer isso de forma funcional, sem efeito colateral e sem reatribuições e ainda pode diminuir algumas linhas do código. A forma que você pode fazer isso é, por exemplo, por meio do método reduce(). Mas como isso pode ser feito? Vamos lá, acompanhe comigo codando passo a passo. Você pode criar uma função própria para obter a pontuação, essa função se chamará getScore, vamos declarar um parâmetro que também se chamará userAnswers e esse parâmetro também tera um default parameter de array vazio. Por esse momento, vamos manter uma lógica similar a que você usou dentro dessa nova função:

const getScore = (userAnswers = []) => {
  userAnswers.forEach((userAnswer, index) => {
    const correctAnswer = userAnswer === correctAnswers[index]

    if (correctAnswer){
      score += 25
    }
  })
}

Depois, a let score que antigamente recebia o valor 0, agora vai ser uma const score e irá receber o retorno da expressão que antes era do forEach, mas agora contém o método reduce. O reduce vai receber um parâmetro chamado accumulator e como segundo argumento, ele irá receber um valor inicial 0. Por quê 0? Porque 0 é o ponto de partida do valor que você deseja obter na pontuação e porque é uma boa prática usar o segundo argumento do reduce para indicar visualmente o tipo de valor que o reduce deve retornar. Feito isso, se a resposta estiver correta, o valor de accumulator somado com 25 será retornado, se a condição do if não for verdadeira, apenas o valor de accumulator vai ser retornado, ficaria assim:

const getScore = (userAnswers = []) => {
  const score = userAnswers.reduce((accumulator,userAnswer,index) => {
    const correctAnswer = userAnswer === correctAnswers[index]

    if (correctAnswer){
      return accumulator + 25
    }

    return accumulator
  }, 0)

  return score
}

Na primeira execução dessa função, accumulator vai receber o segundo argumento do reduce, userAnswer vai receber o primeiro item do array e o index é o mesmo index do item do array. Se a resposta estiver correta, a função vai retornar accumulator + 25, que na primeira execução será (0 + 25). Por outro lado, se a resposta não estiver correta, accumulator será retornado (na primeira execução, o accumulator estará armazenando 0). Quando a função for executada pela segunda vez, o parâmetro accumulator estará armazenando o valor que foi retornado na execução passada da função, então se na primeira execução for retornado accumulator + 25, na segunda execução o parâmetro accumulator estará armazenando 25 ao invés de 0 como na primeira execução. Assim, a operação será feita novamente. E isso vai se repetir para cada item do array userAnswers. A const score está recebendo o resultado dessa expressão e por fim, esse valor é retornado.

Há mais algumas refatorações que você pode fazer para o código se tornar ainda mais conciso, você pode eliminar os returns para aproveitar o return implícito da arrow function e ao invés de usar o bloco de if, você pode usar um ternário, você também ira aprender mais sobre o ternário posteriormente, mas para você ter uma ideia, se o que está a esquerda do sinal de ? for um valor truthy, accumulator + 25 é retornado, caso contrário, apenas accumulator será retornado. O score pode ser retornado diretamente:

// Usando o operador ternário no lugar do bloco do if
const getScore = (userAnswers = []) => { 
  return userAnswers.reduce((accumulator, userAnswer, index) => {
    return userAnswer === correctAnswers[index] ? accumulator + 25 : accumulator
  }, 0)
}
// Usando o return implícito das arrow functions
const getScore = (userAnswers = []) => userAnswers
  .reduce((accumulator, userAnswer, index) => userAnswer === correctAnswers[index] ? accumulator + 25 : accumulator, 0)

Por fim, você pode invocar a função getScore() dentro da função showScore recebendo o array armazenado por userAnswers como argumento. A função atualizada ficaria assim:

const showScore = event => {
  event.preventDefault()

  const userAnswers = getUserAnswers()
  const score = getScore(userAnswers)

  const scoreFeedback = scoreMessage(score)

  // ...
}

showPopup

Na função showPopup, não é necessário que você remova a classe para exibir o popup, você pode adicionar a classe d-flex sem prejudicar nenhuma funcionalidade e ainda assim, o código estará funcionando normalmente. Além disso, a função terá um parâmetro a menos e se tornará mais curta. Ficaria assim:

// antes
const showPopup = (popup, removeClass, addClass, scoreFeedback) => {
  popup.classList.remove(removeClass)
  popup.classList.add(addClass)

  popup.innerHTML = `
    <div class="card" style="width: 28rem;">
      <button class="close-card">X</button>
      <div class="card-body d-flex flex-column justify-content-center">
        <h5 class="card-title text-center">${scoreFeedback}</h5>
        <a href="#" class="btn btn-primary">Reiniciar</a>
      </div>
    </div>
  `
}
// depois
const showPopup = (popup, addClass, scoreFeedback) => {
  popup.classList.add(addClass)

  popup.innerHTML = `
    <div class="card" style="width: 28rem;">
      <button class="close-card">X</button>
      <div class="card-body d-flex flex-column justify-content-center">
        <h5 class="card-title text-center">${scoreFeedback}</h5>
        <a href="#" class="btn btn-primary">Reiniciar</a>
      </div>
    </div>
  `
}

removePopup

Uma outra sugestão seria, se você achar que faz sentido, fazer um destructuring ao invés de usar a sintaxe de colchetes na const elementPopup. Você pode tornar o código mais legível ao usar uma abordagem envolvendo o destructuring. Na const elementPopup, a sintaxe de colchetes está sendo usada para pegar o primeiro elemento do array ao clicar em qualquer lugar na tela. Isso também pode ser feito por meio do destructuring. Ao envolver por colchetes a const elementPopup, você está colocando-a na primeira posição no destructuring de array, ou seja, o primeiro item do array vai para a essa const, pois ela se torna o primeiro item do destructuring. Ficaria assim:

// antes
const removePopup = event => {
  const elementPopup = event.target.classList[0]
  // ...
}
// depois
const removePopup = event => {
  const [elementPopup] = event.target.classList
  // ...
}

Há também um outro detalhe, para a funcionalidade de fechar a janela do popup, você não precisa especificar cada uma das classes que o elementPopup pode armazenar. Ao invés disso, você pode deixar esse código muito mais curto ao fazer com que closePopup receba apenas elementPopup, sabendo que o mesmo irá receber uma das três classes dependendo de onde foi o click, a lógica do if para fechar o popup continuará funcionando normalmente. Ficaria assim:

// antes
const closePopup = elementPopup === 'btn' || elementPopup === 'close-card' || elementPopup === 'wrapper-card'
// depois
const closePopup = elementPopup

Por fim, se fizer sentido para você, a sugestão aqui é colocar a linha do form.reset fora do bloco do if, a ideia aqui é manter no bloco do if apenas o código referente ao fechamento do popup, a vantagem aqui é para que assim esse trecho se responsabilize por apenas esse funcionamento. O código completamente refatorado dessa função seria assim:

// antes
const removePopup = event => {
  const [elementPopup] = event.target.classList
  const closePopup = elementPopup

  if (closePopup) {
    quizResultPopup.classList.remove('d-flex')
    quizResultPopup.classList.add('d-none')

    form.reset()
  }
}
// depois
const removePopup = event => {
  const [elementPopup] = event.target.classList
  const closePopup = elementPopup

  if (closePopup) {
    quizResultPopup.classList.remove('d-flex')
    quizResultPopup.classList.add('d-none')
  }
  form.reset()
}

Nas aulas seguintes, você verá outras formas de refatorar o código dessa aplicação.

Mais uma vez, parabéns pelo esforço =)

As observações fizeram sentido?

MateusCavalheiro-prog commented 1 year ago

Sim as observações fizeram sentido! Vou prestar mais atenção sobre padronizar a escrita do código era algo que eu nem tinha pensado antes.

Fiquei com uma dúvida sobre o map, vou tentar explicar com as minhas palavras. Eu entendi que o map retorna um novo array com a mesma quantidade de itens do array correctAnawers(o array das respostas certas), eu entendi que você "omitiu" o primeiro parâmetro do map e usou apenas o index para que de forma dinâmica o inputQuestion fosse alterando para "inputQuestion1, 2, 3 etc...",.

Eu joguei um typeof no form e me retornou um objeto, ai eu entendi que como ele era um objeto a notação de colchete fazia sentido, pois você usou template strings para injetar o index que é uma variável local.

Mas eu não consegui entender porque o form é um objeto? As tags HTML também são objetos também?

const getUserAnswers = () => {
  const userAnswers = correctAnswers.map((_, index) => {
  const userAnswer = form[`inputQuestion${index + 1}`].value
  return userAnswer
  })

  return userAnswers
}
Roger-Melo commented 1 year ago

@MateusCavalheiro-prog isso mesmo, tags HTML representam elementos do DOM, e elementos do DOM são objetos.

Isso foi mostrado nas aulas da etapa 05, recomendo que revise para reforçar e, no que tiver dúvidas, é só dar o toque =)

Roger-Melo commented 1 year ago

@MateusCavalheiro-prog só pra complementar o que escrevi acima, tags HTML são usadas pra definir elementos que formam a estrutura e o conteúdo de uma página web.

As tags em si não são consideradas objetos, mas sim instruções que indicam ao navegador como exibir o conteúdo.

Porém, os elementos criados a partir dessas tags são considerados objetos, pq possuem propriedades e métodos que podem ser manipulados através do JavaScript.

Ficou mais claro?

MateusCavalheiro-prog commented 1 year ago

Ahh sim, agora minha mente abriu hahahah ! vou revisar a etapa 05 para fixar essas informações! Obrigado pela atenção.