简体   繁体   中英

How can I have each of my my quiz questions and their choices in different pages in react by axios

I am very new in React. I am preparing a quiz. I have my questions and their choices at the same page. What I want is to have each questions with their choices in different pages. After a button click to a choice I want to pass to the next page's question. How can I do that?

import React from "react";
import axios from "axios";

function Question(props) {
  this.state = { questionNumber: 0 };
  let style = {
    margin: "5px"
  };
  clickHandler = () => {};

  return (
    <div className="Question">
      <h4>{props.question}</h4>
      <ul>
        {props.choices.map((c, i) => (
          <button style={style} clickHandler={this.clickHandler}>
            {c.choice}
          </button>
        ))}
      </ul>
    </div>
  );
}

class QuizApp extends React.Component {
  constructor(props) {
    super(props);
    this.state = { questions: [], choices: [] };
  }

  componentDidMount() {
    axios
      .get(
        "https://private-anon-c06008d89c-quizmasters.apiary-mock.com/questions"
      )
      .then(response => {
        this.setState({ questions: response.data });
      });
  }

  render() {
    return <div>{this.state.questions.map(q => new Question(q))}</div>;
  }
}

Not quite different pages, but you can use the array of questions and answers to give the illusion of different pages, but all in the same component. Just display one element at a time. For example, say response.data from your get request is as follows:

[
  {
    question: 'What is question 1?',
    option1: 'Option 1',
    option2: 'Option 2',
    option3: 'Option 3'
  },
  {
    question: 'What is question 2?',
    option1: 'Option 1',
    option2: 'Option 2',
    option3: 'Option 3'
  },
  {
    question: 'What is question 3?',
    option1: 'Option 1',
    option2: 'Option 2',
    option3: 'Option 3'
  },
]

You want to display one index of that arrat ay a time, so you will need another state attribute called currentQuestionIndex . Start that with 0, so that it can start at the first question from that array.

You also need somewhere to store your answers as you go, so, we'll add an answers state attribute. Both of these should be defined in the constructor, which will now look like this:

  constructor (props, context) {
    super(props, context)
    this.state = {
      currentQuestionIndex: 0,
      questions: [],
      answers: []
    }
  }

The questions state attribute needs to be set in your componentDidMount , so that part in your code looks good so far.

We set up the render part so that it only displays whatever question is on the currentQuestionIndex . I will also already add the Next button so you can navigate to the next question.

It should look something like this:

  render() {
    const { questions, currentQuestionIndex, answers } = this.state

    const { question, option1, option2, option3} = questions[currentQuestionIndex]

    return (<div>
        <h4>{question}</h4>

          <label>
            <input type='radio'
              value={option1}/>
            {option1}
          </label>
        <br/>

          <label>
            <input type='radio'
              value={option2}/>
            {option2}
          </label>
          <br/>
          <label>
            <input type='radio'
              value={option3}/>
            {option3}
          </label>
        <hr/>
        <button>Next</button>
      </div>);
  }

Cool! Now we need to make this radio input buttons actually work by adding the checked prop and adding a handler. This is where our answers state comes into play. The checked prop is a boolean, so we want a radio button to be checked whenever their current value is in the same index they are in the answers array. So this is what our handler will look like:

onChangeOption (value) {
    const { currentQuestionIndex } = this.state
    let answers = [...this.state.answers]
    answers[currentQuestionIndex] = value

    this.setState({answers}) 
  }

Lets update our render function to incorporate the checked prop and the onChangeOption function:

  render() {
    const { questions, currentQuestionIndex, answers } = this.state

    const { question, option1, option2, option3} = questions[currentQuestionIndex]

    return (<div>
        <h1>Question {currentQuestionIndex + 1}</h1>
        <h4>{question}</h4>

          <label>
            <input type='radio'
              checked={answers[currentQuestionIndex] === option1}
              value={option1}
              onChange={(evt) => this.onChangeOption(evt.target.value)}/>
            {option1}
          </label>
        <br/>

          <label>
            <input type='radio'
              checked={answers[currentQuestionIndex] === option2}
              value={option2}
              onChange={(evt) => this.onChangeOption(evt.target.value)}/>
            {option2}
          </label>
          <br/>
          <label>
            <input type='radio'
              checked={answers[currentQuestionIndex] === option3}
              value={option3}
              onChange={(evt) => this.onChangeOption(evt.target.value)}/>
            {option3}
          </label>
        <hr/>
        <button>Next</button>
      </div>);
  }

That should take care of our question handling in that page. But how should we change the page? Lets work on that Next button, shall we?

As mentioned previously, what defines what question is displayed is through it's index using the currentQuestionIndex , so upon clicking on the next button, lets increment that. Here is the onClick handler:

  handleNext () {
     let incrementCurrentQuestionIndex = this.state.currentQuestionIndex + 1
     this.setState({currentQuestionIndex: incrementCurrentQuestionIndex})
  }

This is what the next button should look like:

<button onClick={() => this.handleNext()}>Next</button>

Finally, lets add a few final touches so that this little app can function properly:

1- Since you are only getting your questions on the componentDidMount lifecycle method you will need to add a placeholder while your questions are still loading. So inside the render() method, before rendering your questions, you want to check to see if the questions state is already filled in. Something like this:

render() {
    const { questions, currentQuestionIndex, answers } = this.state

    // Will be rendered before you grab all your questions
    if (!questions.length) {
      return <div> Loading questions...</div>
    }

    // Do the rest...
  }

2- Next, you may want to disable the Next button once your array is at it's end. Of course in your actual app, you will handle it differently, but you need to make sure you don't pass the size of the questions array. For this example, we just disable the Next button, so it will look like this:

 <button disabled={currentQuestionIndex === questions.length - 1} onClick={() => this.handleNext()}>Next</button>

Update: This had an awkward flow, so for this example, it would probably be better just to display a different page when you run out of questions. To do this, on your render() function, just add a condition which displays something saying the quiz has ended if the currentQuestionIndex is larger or has reached the size of the questions array. So something like this BEFORE your questions being rendered:

if (currentQuestionIndex >= questions.length) {
      return (
        <div>
          <h3>End of the Quiz!</h3>
        </div>
      )
    }

The example app has been updated accordingly as well.

tl;dr: Take advantage of the array of questions you get. Only display the question and options of the current index you are on. When you want to move on the next one, just increment the current index by clicking the button.

Here is the fully functional example app:

 //Simulating data coming from axios const questionsArray = [ { question: 'What is question 1?', option1: 'Option 1', option2: 'Option 2', option3: 'Option 3' }, { question: 'What is question 2?', option1: 'Option 1', option2: 'Option 2', option3: 'Option 3' }, { question: 'What is question 3?', option1: 'Option 1', option2: 'Option 2', option3: 'Option 3' }, ] class App extends React.Component { constructor (props, context) { super(props, context) this.state = { currentQuestionIndex: 0, questions: [], answers: [] } } componentDidMount() { // Do your axios call and set the questions state. // For the sake of simplicity,I'll be using my array. this.setState({questions: questionsArray}) } handleNext () { let incrementCurrentQuestionIndex = this.state.currentQuestionIndex + 1 this.setState({currentQuestionIndex: incrementCurrentQuestionIndex}) } onChangeOption (value) { const { currentQuestionIndex } = this.state let answers = [...this.state.answers] answers[currentQuestionIndex] = value this.setState({answers}) } render() { const { questions, currentQuestionIndex, answers } = this.state if (!questions.length) { return <div> Loading questions...</div> } if (currentQuestionIndex >= questions.length) { return ( <div> <h3>End of the Quiz!</h3> </div> ) } const { question, option1, option2, option3} = questions[currentQuestionIndex] return (<div> <h1>Question {currentQuestionIndex + 1}</h1> <h4>{question}</h4> <label> <input type='radio' checked={answers[currentQuestionIndex] === option1} value={option1} onChange={(evt) => this.onChangeOption(evt.target.value)}/> {option1} </label> <br/> <label> <input type='radio' checked={answers[currentQuestionIndex] === option2} value={option2} onChange={(evt) => this.onChangeOption(evt.target.value)}/> {option2} </label> <br/> <label> <input type='radio' checked={answers[currentQuestionIndex] === option3} value={option3} onChange={(evt) => this.onChangeOption(evt.target.value)}/> {option3} </label> <hr/> <button onClick={() => this.handleNext()}>Next</button> </div>); } } ReactDOM.render( <App />, document.getElementById('app') );
 <script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script> <div id="app"></div>

Take a look at this example:

https://codesandbox.io/s/km4538r82r

from this lib:

react-distributed-forms

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM