0

Having issues with a uni assignment. In this assignment, we have some data as answers for a problem. I need to build up a form to allow an answer to be selected. In this assignment, there will only be one answer. So I thought could just use a radio button, styled to look like a button, to avoid having to code in logic for checking how many answers we should have and set that.

To do this, I created a new AnswerGroup component and pass in an array of answers.

This is the array (taken using a console.log to show the structure):

Array(5) [ {…}, {…}, {…}, {…}, {…} ]
​0: Object { chosen: 0, text: "10 \\mbox{ kg.}", correct: false }
​1: Object { text: "62 \\frac{2}{9} \\mbox{ kg.}", chosen: 0, correct: true }
​2: Object { text: "None of the above", chosen: 0, correct: false }
​3: Object { text: "1\\frac{17}{28} \\mbox{ kg.}", chosen: 0, correct: false }
​4: Object { chosen: 0, correct: false, text: "1260 \\mbox{ kg.}" }
​length: 5

To clarify the data structure in this object: Chosen is meant to be used at a later stage, where the number of times a user has picked this answer is recorded (it is held in cloudstore, so ultimately that info feeds back) text the text which will appear on the button (it is mathjax format, so looks strange) correct a boolean representing if this is the correct answer or not.

And my AnswerComponent looks like this:

import React, {useState} from 'react';
import { MathComponent } from 'mathjax-react'
import '../styles/radioStyles.css'

function AnswerComponent(props) {
  let answers = props.answersarray; 

  const [value, setValue] = useState(false)

  function handleChange() {
    setValue(value);
    // alert(value)    
  }

  return (
    <div className="col-12">
      <h3 className="text-center">Answer</h3>
      <div className="p-3 mb-2 bg-light">
        <div className="row">
          <div className="answerGroup">
            {
            answers.map((answer, id) => {

              let formulaButton;
              let label = answer.text;
                      
              label.includes('kg') ? formulaButton = true : formulaButton= false;
                      
              return (
              <>
              <input type='radio'
                    //  className={isActive ? 'radBtn' : 'selectedAnswer'}
                    className={'radBtn'}
                    //  checked={value}
                    name='answer'
                    id={id}
                    onChange={handleChange}
                    value={answer.correct}
              />
              <label for={id}>{formulaButton === true ? <MathComponent tex={answer.text} /> :answer.text}</label>
              </>            
              )
            })
            }
          </div>          
        </div>
        <div className="col-sm text-center">
          <button className='btn-primary mb-2 p-4'>CHECK MY ANSWER</button>
        </div>
      </div>
    </div>     
  )
}

export default AnswerComponent;

I have styled with the following:

/* .answerGroup input[type="radio"] {
  opacity: 0;
} */

.answerGroup label {
  background-color: #95a5a6;
  border: none;
  padding: 15px 32px;
  border-radius: 8px;
  text-align: center;
  color: white;
  box-shadow: 0 0.2rem #798d8f;
  text-shadow: 0 0.075rem #617374;
}

.answerGroup label:hover {
  background-color: #a3b1b2;
  box-shadow: 0 0.2rem #87999a;
}  

.selectedAnswer {
  background-color: #FF0000;
  border: none;
  padding: 15px 32px;
  border-radius: 8px;
  text-align: center;
  color: white;
  box-shadow: 0 0.2rem #798d8f;
  text-shadow: 0 0.075rem #617374;
}```

Updated question:
When I click on an individual radio button, I would like the background colour to change (I had created the selectedAnser class in the CSS - and I know that it duplicates the answergroup label class except the background colour)

What I want is that when the buttons are created, if I click on a check answer button, that if the radio with "correct=true" is selected, then the user gets a "well done" (now at this point, that button is somewhere else, but I am refactoring this on the fly - so that button **Could** end up in the react with the answer group - in fact, it is probably a good place for it.
4
  • As I can see you have multiple answers but I only one state for value. Is this desired? Commented Mar 6, 2022 at 9:50
  • 1
    Setting display: none means that those input elements aren't rendered so aren't clickable. I haven't looked closely at the rest of your code so don't know exactly what would work, but try opacity: 0 perhaps? Commented Mar 6, 2022 at 10:09
  • @AHaworth - thanks - that was a mistake on my part Commented Mar 6, 2022 at 11:37
  • @MatthiasWiedemann - I was surprised when only one state was needed, at least it seemed to work (though that may fail on the next part where I try to use the selected item) Commented Mar 6, 2022 at 11:38

1 Answer 1

1

As @a-haworth correctly pointed out the culprit is this block of code.

.answerGroup input[type="radio"] {
  display: none;
}

I believe you added this to get the mathjax render/styled correctly but it straight up hides the radio buttons. So you can comment this block in your css file.

I wasn't able to render the equations so I changed <label> to <button> (changed the css accordingly). This renders the equations inside of the button.

Note: You still have to click on the radio button instead of the grey button with equation; to select.

Update

You are right to think about using this line.

className={isActive ? 'radBtn' : 'selectedAnswer'}

But it should really be used as a prop for <label> instead of <input> since those radio buttons are just small blue circles whose color can't be changed. The bg-color you can change is the <label>. It would have been simple if label had some css selector like checked and would act like a radio button but I couldn't find any. So we are stuck with using react for changing the color (IMO this is the better way!)

There is still a remaining problem though. You can't get away with one value = true/false you need an array of boolean (or a number for the id of the option) to know which option is being selected. Then conditionally setting the class for <label> would get our job done.

Code

function AnswerGroup(props) {
  const answers = props.answersarray;
  // console.log('> ', answers);
  const [values, setValues] = useState(() =>
    answers.map((a) => false)
  );
  // console.log(values);
  const handleChange = (event) => {
    const option_id = Number(event.target.value);
    setValues(
      values.map((a, idx) => (option_id === idx ? true : false))
    );
  };

  return (
    <div className='col-12'>
      <h3 className='text-center'>Answer</h3>
      <div className='p-3 mb-2 bg-light'>
        <div className='row'>
          <div className='answerGroup'>
            {answers.map((answer, id) => {
              let formulaButton;
              let label = answer.text;

              label.includes('kg')
                ? (formulaButton = true)
                : (formulaButton = false);

              return (
                <React.Fragment key={id}>
                  <input
                    type='radio'
                    className={'radBtn'}
                    name='answer'
                    onChange={handleChange}
                    value={id}
                  />
                  <label
                    className={
                      values[id] ? 'selected' : 'notSelected'
                    }
                    forhtml={id}
                  >
                    {formulaButton === true ? (
                      <MathComponent tex={answer.text} />
                    ) : (
                      answer.text
                    )}
                  </label>
                </React.Fragment>
              );
            })}
          </div>
        </div>
        <div className='col-sm text-center'>
          <button className='btn-primary mb-2 p-4'>
            CHECK MY ANSWER
          </button>
        </div>
      </div>
    </div>
  );
}
/* .answerGroup input[type="radio"] {
  opacity: 0;
} */

.notSelected {
  background-color: #95a5a6;
  border: none;
  padding: 15px 32px;
  border-radius: 8px;
  text-align: center;
  color: white;
  box-shadow: 0 0.2rem #798d8f;
  text-shadow: 0 0.075rem #617374;
}

.answerGroup label:hover {
  background-color: #a3b1b2;
  box-shadow: 0 0.2rem #87999a;
}

.selected {
  background-color: #00ccff;
  border: none;
  padding: 15px 32px;
  border-radius: 8px;
  text-align: center;
  color: white;
  box-shadow: 0 0.2rem #798d8f;
  text-shadow: 0 0.075rem #617374;
}
Sign up to request clarification or add additional context in comments.

7 Comments

That was very stupid of me - commenting out the display none lets me see the radio - opacity still shows a click. But - what css would I use to have a different background colour used when a radio button is clicked? I've tried using .answerGroup input[type="radio"]:checked but this keeps the original grey colour
I didn't see the issue with the clicking on the radio until now - that's going to be a problem
Updated the question with the code - I fixed the issue Nikhil pointed out, but still having some styling issues
You should implement this logic in the component answerGroup itself. You have to do two things. First update the handleChange function to update the state answer.chosen. Check this out . Second, Since <button> accepts a prop style where you can add css properties to it. Do something like style={{ color: choosen ? 'blue' : 'grey' }}. I'll update my answer accordingly.
I just realized that this is you assignment so you try adding conditional styles yourself I have already provides some hints to work on also you have to make props.answers as a state in the component even update the answers to have id. Get back here if you get stuck somewhere.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.