akirahin
akirahin

Reputation: 25

radio inputs repeating on selection (React)

I'm trying to make a single question quiz app with react on codepen. I'm using an api to get a question, 3 incorrect answers and a correct answer to the question and adding it to the state. I am having an issue while validating the answer. After clicking on one of the 4 options, I want to conditionally render a div with the result. But the issue is that after the result is displayed, another radio input is getting rendered.

const appRoot = document.getElementById('app');

class App extends React.Component{
  state={
    question:'',
    correct:'',
    incorrect:[],
    result: null
  }

componentDidMount(){
  axios.get('https://opentdb.com/api.php?amount=1&type=multiple')
  .then(res => {
    const data = res.data.results[0];
    const q = data.question;
    const correct = data.correct_answer;
    const incorrect = data.incorrect_answers;
    this.setState({question:q,correct:correct,incorrect:incorrect})
  })
}

evaluate(selected){
  if(selected === this.state.correct){
    this.setState({result: 'Correct!'})
  } else {
    this.setState({result:`Wrong! Correct Answer is ${this.state.correct}`})
  }
}

render(){
  const random = Math.floor(Math.random() * 3);
  const options = this.state.incorrect;
  options.splice(random, 0, this.state.correct);
  const choices = options.map((option,i) => (
    <div>
      <input type='radio' 
     name='option' 
     value={option} 
     key={i} onChange={() => this.evaluate(option)}/>
      <label for='option'>{option}</label>
    </div>
  ) )
  return(
    <div>
      <div>{this.state.question}</div>
      <div>{choices}</div>
      <div>{this.state.result}</div>
    </div>
  )
}
}

ReactDOM.render(<App />, appRoot)
<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>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<div id='app'></div>

Upvotes: 1

Views: 50

Answers (1)

Daniel B. Chapman
Daniel B. Chapman

Reputation: 4687

This seems like an honest mistake (one I am guilty of consistently):

In your render method you mutate your array:

const options = this.state.incorrect;
options.splice(random, 0, this.state.correct);

I suspect what you want is:

const options = this.state.incorrect.slice(); //create a COPY of the array
options.splice(random, 0, this.state.correct);

React is basically a state machine that looks for changes to the state and intelligently updates parts that depend on that state. But it is just JavaScript. By directly splicing the state object you're changing the state in the render method. React doesn't know you changed the state and as such, you're ending up with unexpected behaviors--particularly as render is called frequently.

I've copied your snippet so you can see a working example.

const appRoot = document.getElementById('app');

class App extends React.Component{
  state={
    question:'',
    correct:'',
    incorrect:[],
    result: null
  }

componentDidMount(){
  axios.get('https://opentdb.com/api.php?amount=1&type=multiple')
  .then(res => {
    const data = res.data.results[0];
    const q = data.question;
    const correct = data.correct_answer;
    const incorrect = data.incorrect_answers;
    this.setState({question:q,correct:correct,incorrect:incorrect})
  })
}

evaluate(selected){
  if(selected === this.state.correct){
    this.setState({result: 'Correct!'})
  } else {
    this.setState({result:`Wrong! Correct Answer is ${this.state.correct}`})
  }
}

render(){
  const random = Math.floor(Math.random() * 3);
  const options = this.state.incorrect.slice();
  options.splice(random, 0, this.state.correct);
  const choices = options.map((option,i) => (
    <div>
      <input type='radio' 
     name='option' 
     value={option} 
     key={i} onChange={() => this.evaluate(option)}/>
      <label for='option'>{option}</label>
    </div>
  ) )
  return(
    <div>
      <div>{this.state.question}</div>
      <div>{choices}</div>
      <div>{this.state.result}</div>
    </div>
  )
}
}

ReactDOM.render(<App />, appRoot)
<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>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<div id='app'></div>

Upvotes: 1

Related Questions