0
votes

How do I alter this JS so it works inside of a react component? When I add an onClick handler to the span button I get "cannot read property classList of undefined". This was JS taken out of a non react project so I understand that I'm probably not doing this as best practice, but need the direction to make it work.

Here is my code.

import React from "react"

const Faq = () => {

  const toggleFAQ = function(question) {
    if( question.parentElement.classList.contains('is-opened') ) {
      question.parentElement.classList.remove('is-opened');
    } else {
      question.parentElement.classList.add('is-opened');
    }
  }
  if( document.querySelector('.faq') !== null ) {
    const questions = Array.from( document.getElementsByClassName('faq__question') );
  
    questions[0].parentElement.classList.add('is-opened');
  
    questions.forEach((question) => {
      question.addEventListener('click', (e) => toggleFAQ(question));
    })
  }
 
  return (
    <>
      <section className="faq">
        <div className="faq__inner">
          <h2 className="faq__title">Frequently Asked Questions</h2>
          <ul className="faq__list">
            <li className="faq__item">
              <h3 className="faq__question">
                How do I make this work?
                <span className="faq__question-btn" onClick={toggleFAQ}></span>
              </h3>
              <article className="faq__answer is-opened">
                <div className="faq__answer-inner is-opened">
                  <p>
                    Lorem Ipsum
                  </p>
                </div>
              </article>
            </li>
            <li className="faq__item">
              <h3 className="faq__question">
                Help me, Lord?
                <span className="faq__question-btn"></span>
              </h3>
              <article className="faq__answer">
                <div className="faq__answer-inner">
                  <p>
                    You suck at JS
                  </p>
                </div>
              </article>
            </li>
            <li className="faq__item">
              <h3 className="faq__question">
                How does this work. Help?
                <span className="faq__question-btn"></span>
              </h3>
              <article className="faq__answer">
                <div className="faq__answer-inner">
                  <p>
                    Lorem Ipsum.
                  </p>
                </div>
              </article>
            </li>
            <li className="faq__item">
              <h3 className="faq__question">
                Lorem Ipsum?
                <span className="faq__question-btn"></span>
              </h3>
              <article className="faq__answer">
                <div className="faq__answer-inner">
                  <p>
                    Lorem ipsum
                  </p>
                </div>
              </article>
            </li>
            <li className="faq__item">
              <h3 className="faq__question">
                Lorem Ipsum?
                <span className="faq__question-btn"></span>
              </h3>
              <article className="faq__answer">
                <div className="faq__answer-inner">
                  <p>
                    Lorem Ipsum
                  </p>
                </div>
              </article>
            </li>
          </ul>
        </div>
      </section>
    </>
  )
}
export default Faq

Thanks in advance!

1
Other than you shouldn't query the DOM directly or mutate it in react, can you be more specific about which classList is being accessed, and include a set of reproduction steps? - Drew Reese
The first argument that is going to be passed to toggleFAQ is the SyntheticEvent object passed by react, so question is not going to be your element hence why you get the error - Patrick Evans

1 Answers

0
votes

In React, you don't manipulate the DOM and classLists directly like you are doing / would do in vanilla JS. Instead, it's better to store some state, manipulate the state and then render your view based on the data.

So, I would probably do something like this:

import React, { useState } from "react"

const Faq = () => {
  const [questions, setQuestions] = useState([
    { id: 1, title: 'How do I make this work?', content: 'Lorem Ipsum', isOpen: true },
    { id: 2, title: 'Help me, Lord?', content: 'You suck at JS', isOpen: false },
    { id: 3, title: 'How does this work. Help?', content: 'Lorem Ipsum.', isOpen: false },
    { id: 4, title: 'Lorem Ipsum?', content: 'Lorem ipsum', isOpen: false },
    { id: 5, title: 'Lorem Ipsum?', content: 'Lorem ipsum', isOpen: false },
  ])

  const toggleFAQ = (questionId) => () => {
    const newQuestions = [
      ...questions,
    ]
    const questionIndex = questions.findIndex(q => q.id === questionId)
    newQuestions[questionIndex].isOpen = !newQuestions[questionIndex].isOpen
    setQuestions(newQuestions)
  }
 
  return (
    <section className="faq">
      <div className="faq__inner">
        <h2 className="faq__title">Frequently Asked Questions</h2>
        <ul className="faq__list">
          {questions.map(q => (
            <li key={q.id} className="faq__item">
              <h3 className="faq__question">
                {q.title}
                <span className="faq__question-btn" onClick={toggleFAQ(q.id)}></span>
              </h3>
              <article className={`faq__answer ${q.isOpen ? 'is-opened' : ''}`}>
                <div className={`faq__answer-inner ${q.isOpen ? 'is-opened' : ''}`}>
                  <p>
                    {q.content}
                  </p>
                </div>
              </article>
            </li>
          ))}
        </ul>
      </div>
    </section>
  )
}

export default Faq