Allen H.
Allen H.

Reputation: 358

React - How to let this functional component change div styles?

I am a bit of a react newbie so please be gentle on me. I am trying to build an education app, and code is to render multiple choice answer boxes:

export default function AnswerBox(props: any) {
    return (
        <div className="answer-container">
            <ul>
                {props.answers.map((value: any) => {
                    return (
                    <li className="answer-box" key={value.letter} id={value.letter}>
                        <input className="answer-textbox" type="checkbox" onChange={() => console.log('selected: ', value.letter)}></input>
                        <span className="answer-letter"><b>{value.letter})</b></span>
                        {value.answer}
                    </li>)
                })
                }
            </ul>
        </div>
    )
}

As you can see, the function takes a object of arrays and iterates through arrays to display the Question Letter (e.x. 'A') and Question Answer in an unordered list.

So all of this is good, but I'd like the list element to be highlighted or have the answerbox div changed when it is actually selected. And I havent found a good way to do that other than to change the component into a stateful component and use a state var to track which box is ticked.

But when I changed it to a stateful component last night, I really struggled to pass in the list of objects to render.

How can I have a stateful class that also accepts props like a regular functional component?

Upvotes: 0

Views: 2225

Answers (2)

C.RaysOfTheSun
C.RaysOfTheSun

Reputation: 716

To begin, you pass props to all types of components in a similar fashion, regardless if it's stateful or not.

 <Component prop={someValue}/>

The only difference is how you would access them.

For class-based components you would access them through the props property of the class this.props. i.e.

class Component extends React.Component {
  constructor(props) {
     // you need to call super(props) otherwise
     // this.props will be underfined
     super(props);
  }

  ...
  someFunction = (...) => {
     const value = this.props.prop;
  }
}

If you're using TypeScript, you need to describe to it the structure of your props and state like this

interface iComponentProps { 
    prop: string;
};

interface iComponentState { ... };

export default class Component extends React.Component<iComponentProps, iComponentState> { 
  ... 
}

if your component takes in props and/or state and you're unsure of their structure, pass in any for the one you're unsure of.


On the other hand, if I understood your question correctly, you could do something like this:

I also made a demo of the simple app I made to address your other question.

In summary, you can have your AnswerBox component maintain an array of indexes that pertain to each of its choices and have it updated every time a choice is selected (or clicked) by using setState

You can also check out the useState hook to make your functional component stateful.

App.js

import React from "react";
import "./styles.css";
import Question from "./Question";

export default function App() {
  const questionData = [
    {
      question: "Some Question that needs to be answered",
      choices: ["Letter A", "Letter B", "Letter C"]
    },
    {
      question: "Another Question that needs to be answered",
      choices: ["Letter A", "Letter B", "Letter C"]
    }
  ];
  return (
    <div className="App">
      {questionData.map(question => (
        <Question questionText={question.question} choices={question.choices} />
      ))}
    </div>
  );
}

Question.js

import React from "react";
import AnswerBox from "./AnswerBox";

const Question = ({ questionText, choices }) => {
  return (
    <div className={"question-container"}>
      <p className={"question-text"}>{questionText}</p>
      <AnswerBox choices={choices} />
    </div>
  );
};

export default Question;

QuestionChoice.js

import React from "react";
import clsx from "clsx";

const QuestionChoice = ({ letter, content, isSelected, handleClick }) => {
  return (
    <li
      className={clsx("question-choice-container", {
        "selected-choice": isSelected
      })}
    >
      <input type={"checkbox"} value={content} onClick={handleClick} />
      <label for={content} className={"question-choice-label"}>
        <strong>{letter.toUpperCase()}. </strong>
        <span>{content}</span>
      </label>
    </li>
  );
};

export default QuestionChoice;

AnswerBox.js

import React, { PureComponent } from "react";
import QuestionChoice from "./QuestionChoice";

export default class AnswerBox extends PureComponent {
  constructor(props) {
    super(props);
    this.choiceLetters = Array.from("abcdefghijklmnopqrstuvwxyz");
    this.state = { activeChoices: [] };
  }

  _updateActiveChoices = index => {
    let updatedList = [].concat(this.state.activeChoices);
    if (this.state.activeChoices.indexOf(index) !== -1) {
      updatedList.splice(updatedList.indexOf(index), 1);
    } else {
      updatedList.push(index);
    }

    return updatedList;
  };

  _handleChoiceSelect = choiceIndex => () => {
    // an update to your component's state will
    // make it re-run its render method
    this.setState({ activeChoices: this._updateActiveChoices(choiceIndex) });
  };

  render() {
    return (
      <ul class={"answer-box"}>
        {this.props.choices.map((choice, index) => (
          <QuestionChoice
            letter={this.choiceLetters[index]}
            content={choice}
            isSelected={this.state.activeChoices.indexOf(index) != -1}
            handleClick={this._handleChoiceSelect(index)}
          />
        ))}
      </ul>
    );
  }
}

Upvotes: 1

William Chou
William Chou

Reputation: 772

For this type of interaction you can use the input checkbox's checked attribute to show that its checked. The check state should be derived from somewhere in your state. In the onClick function, you can look at the event for the name and checked state to update your state. Note you could add any attribute you want if name is too generic for you.

The interaction could look like this:

https://codesandbox.io/s/optimistic-archimedes-ozr72?file=/src/App.js

Upvotes: 0

Related Questions