Artigo original: https://www.freecodecamp.org/news/how-to-build-a-quiz-app-using-react/

Neste tutorial de React para iniciantes, vamos criar uma aplicação de questionário. Trabalharemos com objetos de state complexos, como lidar com diferentes hooks de state e produziremos algo com base neles.

Dê uma olhada:

Tente você mesmo

Se você quiser tentar primeiro, aqui estão os cenários (você também pode obter o código inicial abaixo):

  • Quando o usuário clica em um botão, a próxima pergunta deve ser exibida
  • Se o usuário acertar a pergunta, a pontuação deverá ser aumentada
  • Quando o usuário chegar ao final do questionário, sua pontuação total deverá ser exibida

Tutorial

Faça o download do código inicial no GitHub aqui.

Vamos lá!

Se você abrir o código inicial e acessar App.js, verá que forneci uma lista de perguntas/respostas, armazenadas em um array chamado questions. Esse é o nosso questionário.

Nosso primeiro objetivo é pegar os dados das perguntas no array e exibi-los na tela.

Removeremos o texto inserido diretamente no código e usaremos os dados da primeira pergunta, por enquanto, só para começar. Vamos nos preocupar com a troca de perguntas mais tarde.

Em nosso JSX, remova o texto da pergunta inserido diretamente no código e digite {questions[0]} para obter o primeiro item (ou pergunta) em nosso array de perguntas.

<div className='question-text'>{questions[0]}</div>

Renderização de perguntas e respostas

A primeira pergunta é um tema. Portanto, podemos usar a "notação de ponto" para obter acesso às propriedades. Agora, usaremos apenas {question[0].questionText} para obter acesso ao texto da pergunta para esse objeto:

<div className='question-text'>{questions[0].questionText}</div>

Salve e execute a aplicação. Observe como o texto é atualizado. Lembre-se de que estamos apenas pegando o texto da primeira pergunta do primeiro tema em nosso array de perguntas.

Adotaremos uma abordagem semelhante para as opções de resposta. Remova os botões inseridos diretamente no código e use a função map() para repetir as opções de resposta de uma determinada pergunta.

Lembre-se de que a função map() faz uma repetição sobre o array e nos fornece o item atual em que a repetição se encontra, por meio de uma variável.

Substitua a div com a classe "answer-section" pela seguinte:

<div className='answer-section'>
	{questions[0].answerOptions.map((answerOption, index) => (
		<button>{answerOption.answerText}</button>
	))}
</div>

Salve e execute a aplicação. Observe como quatro botões de resposta aparecem e o texto é renderizado dinamicamente.

Vamos recapitular:

  • Estamos obtendo a primeira pergunta do array de perguntas: questions[0]
  • A primeira pergunta é um tema que contém um array de answerOptions. Podemos chegar a esse array usando a notação de ponto: questions[0].answerOptions
  • Como o answerOptions é um array, podemos mapeá-lo: questions[0].answerOptions.map
  • Dentro da função map(), renderizamos um botão para cada answerOption e exibimos o texto

Alterando as perguntas usando o state

Agora, vamos voltar ao nosso JSX. Observe como, se alterarmos questions[0] para questions[1] ou questions[2], a interface do usuário será atualizada. Isso ocorre porque ele está obtendo os dados de diferentes perguntas em nosso array de perguntas, dependendo do índice.

O que queremos fazer é usar um tema de estado para manter a pergunta em que o usuário está no momento e atualizá-la quando um botão de resposta for clicado. Você pode ver isso ao executar o código no exemplo final.

Vá em frente e adicione um objeto de state, que conterá o número da pergunta atual em que o usuário está. Ele será inicializado em 0 para que o questionário pegue a primeira pergunta do array:

const [currentQuestion, setCurrentQuestion] = useState(0);

Agora, queremos substituir o "0" incorporado diretamente no código em nosso JSX por essa variável. Primeiro, para o texto da pergunta:

<div className='question-text'>{questions[currentQuestion].questionText}</div>

Para a seção de perguntas, temos:

<div className='answer-section'>
	{questions[currentQuestion].answerOptions.map((answerOption, index) => (
		<button>{answerOption.answerText}</button>
	))}
</div>

Agora, se você inicializar a currentQuestion com algo diferente de 0, por exemplo, 1 ou 2, a interface do usuário será atualizada para mostrar a pergunta e as respostas dessa pergunta específica. Muito legal!

Vamos adicionar algum código para que, quando clicarmos em uma resposta, possamos incrementar o valor de currentQuestion para passarmos para a próxima pergunta.

Crie uma função chamada handleAnswerButtonClick. Ela será chamada quando o usuário clicar em uma resposta.

Vamos incrementar o valor da pergunta atual em um, salvá-lo em uma nova variável e definir essa nova variável no estado:

const handleAnswerButtonClick = (answerOption) => {
	const nextQuestion = currentQuestion + 1;
	setCurrentQuestion(nextQuestion);
};

Em seguida, adicione um evento onClick ao nosso botão, assim:

<button onClick={() => handleAnswerButtonClick()}>{answerOption.answerText}</button>

Se testarmos isso, você verá que funciona, até chegarmos ao final:

error

Então, o que está acontecendo? Bem, em nossa função handleAnswerButtonClick, estamos incrementando o número e definindo-o como o state. Está certo.

Lembre-se, porém, de que usamos esse número para acessar um array, a fim de obter as opções de pergunta e resposta. Quando chegarmos a 5, ele causará um erro, pois não há um quinto elemento!

Vamos fazer uma verificação para garantir que não ultrapassemos o limite. Em nossa função handleAnswerButtonClick, vamos adicionar a seguinte condição:

if (nextQuestion < questions.length) {
	setCurrentQuestion(nextQuestion);
} else {
	alert('you reached the end of the quiz');
}

Isso basicamente diz que, se o número da próxima pergunta for menor que o número total de perguntas, o state será atualizado para a próxima pergunta. Caso contrário, chegamos ao final do questionário e, portanto, um alerta é mostrado, por enquanto.

Exibição da tela de pontuação

Em vez de mostrar um alerta, o que queremos fazer é exibir a tela de "pontuação".

Se olharmos para o JSX, você perceberá que eu coloquei a marcação aqui para você. Só precisamos substituir "false" pela lógica.

Então, como podemos fazer isso? Bem, esse é o objeto perfeito para se colocar em um state!

Adicione outro objeto de state que armazenará se queremos mostrar a tela de pontuação ou não:

const [showScore, setShowScore] = useState(false);

E substitua false por showScore em nosso JSX:

<div className='app'>{showScore ? <div className='score-section'>// ... score section markup</div> : <>// ... quiz question/answer markup</>}</div>

Nada mudará, mas, se alterarmos o valor do estado para "true", a div de pontuação será exibida. Isso ocorre porque tudo está envolvido em um condicional ternário, ou seja:

Se showScore for verdadeiro, renderize a marcação da seção de pontuação; caso contrário, renderize a marcação da pergunta/resposta do teste.

Agora, queremos atualizar essa variável de state quando o usuário chegar ao final do questionário. Já escrevemos a lógica para isso em nossa função handleAnswerButtonClick.

Tudo o que precisamos fazer é substituir a lógica de alerta que atualiza a variável showScore para que seja verdadeira:

if (nextQuestion < questions.length) {
	setCurrentQuestion(nextQuestion);
} else {
	setShowScore(true);
}

Se clicarmos nas respostas do questionário, ele mostrará a seção de pontuação quando chegarmos ao final. No momento, o texto e a pontuação mostrados são uma string inserida diretamente no código. Devemos, então, torná-la dinâmica.

Salvando a pontuação

Nossa próxima tarefa é manter uma pontuação em algum lugar da nossa aplicação e incrementar esse valor se o usuário selecionar a opção correta.

O local lógico para fazer isso é na função "handleAnswerOptonClick".

Lembre-se de que, quando iteramos sobre as answerOptions, a função map() nos fornece um objeto para cada uma delas, que inclui o questionText e um valor booleano que mostra se a resposta está correta ou não. Esse booleano é o que usaremos para nos ajudar a incrementar nossa pontuação.

Em nosso botão, atualize a função da seguinte maneira:

onClick={()=> handleAnswerButtonClick(answerOption.isCorrect)

Em seguida, atualize a função para que aceite o parâmetro a seguir:

const handleAnswerButtonClick = (isCorrect) => {
	//... other code
};

Agora, podemos adicionar alguma lógica aqui em nossa função. Por enquanto, queremos dizer que "se isCorrect for verdadeiro, queremos mostrar um alerta":

const handleAnswerButtonClick = (isCorrect) => {
	if (isCorrect) {
		alert(“the answer is correct!”)
	}

	//...other code
};

Isso é o mesmo que if(isCorrect === true), apenas uma versão abreviada. Agora, se tentarmos fazer isso, você verá que receberemos um alerta quando clicarmos na resposta correta.

Só para recapitular até agora:

  • Quando repetimos os botões, passamos o valor booleano isCorrect desse botão para a função handleAnswerButtonClick.
  • Na função, verificamos se esse valor é verdadeiro e, se for, exibimos um alerta.

Em seguida, queremos realmente salvar a pontuação. Como você acha que podemos fazer isso? Se você disse "valor no state", está correto!

Vá em frente e adicione outro valor no state chamado "score" (pontuação). Lembre-se de prefixar a função para alterar o valor com "set" para que seja setScore. Inicialize-a com 0:

const [score, setScore] = useState(0);

Em seguida, em vez de mostrar um alerta, queremos atualizar nossa pontuação em 1 se o usuário tiver acertado a resposta.

Em nossa função handleAnswerButtonClick, remova o alerta e aumente nossa pontuação em um:

const handleAnswerButtonClick = (isCorrect) => {
	if (answerOption.isCorrect) {
		setScore(score + 1);
	}

	//...other code
};

Exibição da pontuação

Para mostrar a pontuação, basta fazer uma pequena alteração em nosso código de renderização. Em nosso JSX, remova a string inserida diretamente no código na seção de pontuação e adicione essa nova variável:

<div className='score-section'>
	You scored {score} out of {questions.length}
</div>
<div className='score-section'>
	You scored {score} out of {questions.length}
</div>

Agora, se revisarmos as respostas, a pontuação é dinâmica e será exibida corretamente no final!

Uma última coisa antes de encerrarmos nossa aplicação de questionário: você notará que a pergunta atual exibida na interface do usuário é sempre "1", pois isso está inserido diretamente no código. Precisamos mudar isso para que seja mais dinâmico.

Substitua o "question-count" pelo seguinte:

<div className='question-count'>
	<span>Question {currentQuestionIndex + 1}</span>/{questions.length}
</div>

Lembre-se de que precisamos do +1, pois os computadores começam a contar a partir de 0 e não do 1.

Deseja mais ideias para projetos?

Por que não tentar criar alguns projetos em React para melhorar ainda mais seu aprendizado? A cada duas semanas, o autor publica um novo projeto para que você experimente um exemplo funcional, um código inicial e dicas. Inscreva-se para receber as publicações diretamente na sua caixa de mensagens!