Home > Back-end >  React conditional styling in a map function problem
React conditional styling in a map function problem

Time:03-14

I just want to show toggled item. But all map items showing up. Basically this is the result I'm getting from onclick. I think i need to give index or id to each item but i don't know how to do it. i gave id to each question didn't work.

enter image description hereenter image description here

App.js.

import "./App.css";
import React, { useState, useEffect } from "react";
import bg from "./images/bg-pattern-desktop.svg";
import bg1 from "./images/illustration-box-desktop.svg";
import bg2 from "./images/illustration-woman-online-desktop.svg";
import { data } from "./data";
import Faq from "./Faq";

function App() {
  const [db, setDb] = useState(data);
  const [toggle, setToggle] = useState(false);
  useEffect(() => {
    console.log(db);
  }, []);

  return (
    <>
      <div className="container">
        <div className="container-md">
          <div className="faq">
            <img src={bg} className="bg" />
            <img src={bg1} className="bg1" />
            <img src={bg2} className="bg2" />
            <div className="card">
              <h1>FAQ</h1>
              <div className="info">
                {db.map((dat) => (
                  <Faq
                    toggle={toggle}
                    setToggle={setToggle}
                    title={dat.title}
                    desc={dat.desc}
                    key={dat.id}
                    id={dat.id}
                  />
                ))}
              </div>
            </div>
          </div>
        </div>
      </div>
    </>
  );
}

export default App;
(map coming from simple data.js file that I created. it includes just id title desc.)

Faq.js

import React from "react";
import arrow from "./images/icon-arrow-down.svg";

const Faq = ({ toggle, setToggle, title, desc, id }) => {
  return (
    <>
      {" "}
      <div className="question" onClick={() => setToggle(!toggle)}>
        <p>{title}</p>

        <img src={arrow} className={toggle ? "ikon aktif" : "ikon"} />
      </div>
      <p className="answer border">{toggle ? <>{desc}</> : ""}</p>
    </>
  );
};

export default Faq;

CodePudding user response:

You need to store the index value of the toggle item. You can modify the code with only 2 lines with the existing codebase.

import "./App.css";
import React, { useState, useEffect } from "react";
import bg from "./images/bg-pattern-desktop.svg";
import bg1 from "./images/illustration-box-desktop.svg";
import bg2 from "./images/illustration-woman-online-desktop.svg";
import { data } from "./data";
import Faq from "./Faq";

function App() {
  const [db, setDb] = useState(data);
  const [toggle, setToggle] = useState(-1); //Modify Here
  useEffect(() => {
    console.log(db);
  }, []);

  return (
    <>
      <div className="container">
        <div className="container-md">
          <div className="faq">
            <img src={bg} className="bg" />
            <img src={bg1} className="bg1" />
            <img src={bg2} className="bg2" />
            <div className="card">
              <h1>FAQ</h1>
              <div className="info">
                {db.map((dat, index) => ( //Modify Here
                  <Faq
                    toggle={index === toggle} //Modify Here
                    setToggle={() => setToggle(index)} //Modify Here
                    title={dat.title}
                    desc={dat.desc}
                    key={dat.id}
                    id={dat.id}
                  />
                ))}
              </div>
            </div>
          </div>
        </div>
      </div>
    </>
  );
}

export default App;



import React from "react";
import arrow from "./images/icon-arrow-down.svg";

const Faq = ({ toggle, setToggle, title, desc, id }) => {
  return (
    <>
      {" "}
      <div className="question" onClick={setToggle}>
        <p>{title}</p>

        <img src={arrow} className={toggle ? "ikon aktif" : "ikon"} />
      </div>
      <p className="answer border">{toggle ? <>{desc}</> : ""}</p>
    </>
  );
};

export default Faq;

CodePudding user response:

You will need state for each toggle. Here is a minimal verifiable example. Run the code below and click ⭕️ to toggle an item open. Click to close it.

function App({ faq = [] }) {
  const [toggles, setToggles] = React.useState({})
  const getToggle = key =>
    Boolean(toggles[key])
  const setToggle = key => event =>
    setToggles({...toggles, [key]: !getToggle(key) })
  return faq.map((props, key) =>
    <Faq key={key} {...props} open={getToggle(key)} toggle={setToggle(key)} />
  )
}

function Faq({ question, answer, open, toggle }) {
  return <div>
    <p>
      {question}
      <button onClick={toggle} children={open ? "❌" : "⭕️"} />
    </p>
    {open && <p>{answer}</p>}
  </div>
}

const faq = [
  {question: "hello", answer: "world"},
  {question: "eat", answer: "vegetables"}
]

ReactDOM.render(<App faq={faq} />, document.querySelector("#app"))
p { border: 1px solid gray; padding: 0.5rem; }
p ~ p { margin-top: -1rem; }
button { float: right; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.14.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.14.0/umd/react-dom.production.min.js"></script>
<div id="app"></div>

CodePudding user response:

Instead of doing this (in App component):

 const [db, setDb] = useState(data);
 const [toggle, setToggle] = useState(false);

you can write an useState hook like below to combine the two hooks and assign an isOpened property for each Faq element:

const [db, setDb] = useState(data.map(value=>{return {...value, isOpened:false}}));

and then right here you can do this (as the child of <div className="info">):

{db.map((dat, index) => ( 
      <Faq
        toggle={dat.isOpened} 
        setToggle={() => toggleById(dat.id)} 
        title={dat.title}
        desc={dat.desc}
        key={dat.id}
        id={dat.id}
      />
))}

Also you need to declare toggleById function in App component:

const toggleById = (id) => {
   const newDb = db.map(dat=>{
       if(dat.id==id){
          return {...dat,isOpened:!dat.isOpened}
       }
       return dat;
   });
   setDb(newDb);
}

and since setToggle prop of Faq, calls toggleById by its defined parameter, there is no need to do this in Faq component:

<div className="question" onClick={() => setToggle(!toggle)}>

you can simply write:

<div className="question" onClick={setToggle}>
  • Related