Artigo original: How to Build a Quiz App Using React –with Tips and Starter Code

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!