chvc
chvc

Reputation: 95

Show or Hide a particular element in react

I have to show list of faqs and I need to hide the answers of the questions. When I click on the question, the answer for that particular question need to be shown. My problem is, I have a bunch of questions and when i click the button, it will show all of the answer instead of the specific answer to that question.

class Faqs extends Component {
  constructor(props){
    super(props);
    this.state = {
      isHidden: true
    }
  }
  toggleHidden () {
    this.setState({
      isHidden: !this.state.isHidden
    })
  }
render() {
        return (
            <div>
               <span onClick={() => this.toggleHidden()}><strong>This is the question</strong></span>
               {!this.state.isHidden && <p>Answer for the question</p>} <br/>

               <span onClick={() => this.toggleHidden()}><strong>Question2</strong></span>
               {!this.state.isHidden && <p>Answer2</p>} <br/>
               <hr></hr>            
            </div >
        )
    }
}

Upvotes: 4

Views: 20164

Answers (5)

kasperoo
kasperoo

Reputation: 1584

Ideally you would list FAQs in some kind of list - then as you iterate over them, each will have an index number assigned to it - then as you toggle individual answers, you store that index in the state and operate on DOM via that number.

edit. In current day and age, it's only appropriate to show example using hooks:

const {useState} = React;

const FaqApp = () => {
  const [ selectedQuestion, toggleQuestion ] = useState(-1);
  
  function openQuestion(index) {
    toggleQuestion(selectedQuestion === index ? -1 : index);
  }

  const faqs = getFaqs();

  return (
    <div>
      <h2>FAQs:</h2>
        {faqs.map(( { question, answer}, index) => (
          <div key={`item-${index}`} className={`item ${selectedQuestion === index ? 'open' : ''}`}>
            <p className='question' onClick={() => openQuestion(index)}>{question}</p>
            <p className='answer'>{answer}</p>
          </div>
        ))}
    </div>
  )
}

function getFaqs() {
  const faqs = [
    {
      question: 'Question 1',
      answer: 'answer 1'
    },
    {
      question: 'Question 2',
      answer: 'answer 2'
    }
  ];
  return faqs;
}


ReactDOM.render(
  <FaqApp />,
  document.getElementById("react")
);
body {
  background: #fff;
  padding: 20px;
  font-family: Helvetica;
}

#app {
  background: #fff;
  border-radius: 4px;
  padding: 20px;
  transition: all 0.2s;
}

h2 {
   margin-bottom: 11px;
}

.item + .item {
  margin-top: 11px;
}

.question {
  font-weight: bold;
  cursor: pointer;
}

.answer {
   display: none;
}

.open .answer {
  display: block;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="react"></div>


older version of this post:

I've written a quick example that allows you to have multiple questions:

class FaqApp extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      // start the page with all questions closed
    	selectedQuestion: -1
    };
    this.openQuestion = this.openQuestion.bind(this);
  }
	
  getFaqs() {
  	// some service returning a list of FAQs
  	const faqs = [
      {
        question: 'Question 1',
        answer: 'answer 1'
      },
      {
        question: 'Question 2',
        answer: 'answer 2'
      }
    ];
    return faqs;
  }
  
  openQuestion(index) {
    // when a question is opened, compare what was clicked and if we got a match, change state to show the desired question.
  	this.setState({
    	selectedQuestion: (this.state.selectedQuestion === index ? -1 : index)
    });
  }
  
  render() {
    // get a list of FAQs
    const faqs = this.getFaqs();
    return (
      <div>
        <h2>FAQs:</h2>
          {faqs.length && faqs.map((item, index) => (
            <div key={`item-${index}`} className={`item ${this.state.selectedQuestion === index ? 'open' : ''}`}>
                <p className='question' onClick={() => this.openQuestion(index)}>
                  {item.question}
                </p>
                <p className='answer'>
                  {item.answer}
                </p>
            </div>
          ))}
      </div>
    )
  }
}

ReactDOM.render(<FaqApp />, document.querySelector("#app"))
body {
  background: #20262E;
  padding: 20px;
  font-family: Helvetica;
}

#app {
  background: #fff;
  border-radius: 4px;
  padding: 20px;
  transition: all 0.2s;
}

h2 {
   margin-bottom: 11px;
}

.item + .item {
  margin-top: 11px;
}

.question {
  font-weight: bold;
  cursor: pointer;
}

.answer {
   display: none;
}

.open .answer {
  display: block;
}
<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>

Upvotes: 2

vapurrmaid
vapurrmaid

Reputation: 2307

The issue is that you're using one boolean piece of state to control the logic for multiple pieces. This is a classic scenario to use separate components.

Create a new component ToggleQuestion that encapsulates the mechanic of show/reveal.

The Faqs component instead manages a list of ToggleQuestion components.

const QUESTIONS = [
  { title: 'q1', answer: 'a1' },
  { title: 'q2', answer: 'a2' }
]

class ToggleQuestion extends React.Component {
  constructor (props) {
    super(props)
    this.state = { isHidden: true }
  }
  
  toggleHidden () {
    this.setState({ isHidden: !this.state.isHidden })
  }
  
  render () {
    const { question, answer } = this.props
    const { isHidden } = this.state
    return (
      <div>
        <span>{question}</span>
        { !isHidden && <span>{answer}</span> }
        <button onClick={this.toggleHidden.bind(this)}>
          Reveal Answer
        </button>
      </div>
    )
  }
}

class Faqs extends React.Component {
  render () {
    return (
      <div>
        { QUESTIONS.map(question => (
          <ToggleQuestion
            question={question.title}
            answer={question.answer}
          />
        ))}
      </div>
    )
  }
}

ReactDOM.render(<Faqs />, document.getElementById('container'))
<div id='container'></div>
<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>

Upvotes: 1

Sibaprasad Maiti
Sibaprasad Maiti

Reputation: 557

You can break your component to one more level to have a sub component which renders only the question and corresponding answer. Pass the question and answers as props. In that way you can use the same component for all questions and yet every question/answer pair will have their own state.

class Faq extends Component{
 state = {isHidden: true}
 toggleHidden = ()=>this.setState((prevState)=>({isHidden: !prevState.isHidden}))
 render(){
  return(
     <div>
     <span onClick={this.toggleHidden}>
           <strong>{props.question}</strong></span>
           {!this.state.isHidden && <p>{props.answer}</p>}   
     </div>
  )
 }
}




class Faqs extends Component {

render() {
        return (
            <div>
              <Faq question={"Question 1"} answer={"answer 1"} />
              <Faq question={"Question 2"} answer={"answer 2"} />
            </div >
        )
    }
}

Upvotes: 7

Modig
Modig

Reputation: 1036

This is another way to do what you want. (This one will only make it possible to have one open at a time)

class Faqs extends Component {
  constructor(props){
    super(props);
    this.state = {
      hiddenId: null,
    }
  }
  setHiddenId(id) {
    this.setState({
      hiddenId: id
    })
  }
  render() {
        return (
            <div>
               <span onClick={() => this.setHiddenId('one')}><strong>This is the question</strong></span>
               {this.state.hiddenId === 'one' && <p>Answer for the question</p>} <br/>

           <span onClick={() => this.setHiddenId('two')}><strong>Question2</strong></span>
           {this.state.hiddenId === 'two' && <p>Answer2</p>} <br/>
           <hr></hr>            
        </div >
    )
    }
}

Upvotes: 0

Tnc Andrei
Tnc Andrei

Reputation: 1012

I would write a different handler for the answer. In the future if you need more logic for the answer will be scalable. Notice renderAnswer

class Faqs extends Component {
  constructor(props){
    super(props);
    this.state = {
      isHidden: true
    }
  }
  toggleHidden () {
    this.setState({
      isHidden: !this.state.isHidden
    })
  }
  renderAnswer() {
    if (this.state.isHidden) {
      return;
    }
    return (
      <p>Answer</p>
    );
  }
  render() {
    return (
            <div>
               <span onClick={() => this.toggleHidden()}><strong>This is the question</strong></span>
               { this.renderAnswer() } <br/>

               <span onClick={() => this.toggleHidden()}><strong>Question2</strong></span>
               { this.renderAnswer() } <br/>
               <hr></hr>            
            </div >
        )
    }
}

Upvotes: 0

Related Questions