Issue
Here is the component/function which is a card which can be expanded with a button. <Viewmore>
is nothing but a component which is an image. Had to use it because the image was an svg.
import React, { useState, useRef } from "react";
import Rating from "@material-ui/lab/Rating";
import Icon1 from "../test.jpeg";
import Viewmore from "./Viewmore";
function Acc(props) {
const [value, setValue] = React.useState(2);
const content = useRef(null);
const [setActive, setActiveState] = useState("");
const [setHeight, setHeightState] = useState("160px");
const [setRotate, setRotateState] = useState("icon");
function toggleAccordion() {
setActiveState(setActive === "" ? "active" : "");
setHeightState(
setActive === "active" ? "160px" : `${content.current.scrollHeight}px`
);
setRotateState(setActive === "active" ? "icon" : "icon rotate");
}
return (
<div className="accordian" id={props.id}>
<div className="accordianHeading">
<div
className="container"
ref={content}
style={{ maxHeight: `${setHeight}` }}
>
<img src={Icon1} className="postimg" />
<div className="title">
Nulla adipisicing do ea qui enim id cillum reprehenderit aliquip
nostrud sint fugiat.
</div>
<div className="namedate">asdfasdf | 10th April 2021</div>
<div classname="content">
<p>
Aliquip duis excepteur non ullamco eiusmod cillum cupidatat non
officia laborum esse tempor est nisi. Aute amet irure ex proident
commodo reprehenderit cupidatat deserunt dolor eu. Culpa elit
officia sit id reprehenderit nisi amet tempor reprehenderit fugiat
sint irure. Laborum pariatur nisi amet minim eiusmod ad occaecat
ipsum aliquip. Aute dolore culpa ad minim voluptate. Lorem in qui
pariatur nostrud in velit ad sunt irure mollit commodo. Sit
voluptate occaecat cupidatat dolor veniam aute amet sit ea
consequat et voluptate id.
</p>
</div>
</div>
</div>
<div className="accordianContent">
<div className="foot">
<Rating defaultValue={0} precision={1} />
<button className="button" onClick={toggleAccordion}>
<Viewmore className={`${setRotate}`} width={20} fill={"777"} />
</button>
</div>
</div>
</div>
);
}
export default Acc;
When the button is clicked, it sets 3 processes in motion. First the active which triggers an active state. This triggers an active css class as well as rotates the button icon.
Here is the App.js which is routed to ReactDOM.
import React, { useState } from "react";
import "./App.css";
import Acc from "./components/Acc";
function App() {
return (
<div className="App">
<Acc />
<Acc />
<Acc />
<Acc />
<Acc />
<Acc />
<Acc />
<Acc />
</div>
);
}
export default App;
How can I make sure only 1 <Acc />
card is open at a time?
Solution
There are some different ways to do it but here is my suggestion.
The parent (App
) has a state who is the active card. It passes to every child
- Whether it's active or not
- An
onToggle
callback to let the parent to know the it clicked.
The child is listening (useEffect
) on its state (active
) and update the UI accordingly.
App.js
const cards = [1, 2, 3, 4, 5, 6, 7, 8];
export default function App() {
const [active, setActive] = useState();
const onToggle = (id) => {
setActive(id === active ? null : id);
};
return (
<div className="App">
{cards.map((card) => (
<Acc
key={card}
id={card}
onToggle={onToggle}
active={card === active}
/>
))}
</div>
);
}
Acc.js (only the differences)
useEffect(() => {
setHeightState(active ? `${content.current.scrollHeight}px` : "160px");
setRotateState(active ? "icon" : "icon rotate");
}, [active]);
function toggleAccordion() {
onToggle(id);
// setActiveState(setActive === "" ? "active" : "");
// setHeightState(
// setActive === "active" ? "160px" : `${content.current.scrollHeight}px`
// );
// setRotateState(setActive === "active" ? "icon" : "icon rotate");
}
https://codesandbox.io/s/cranky-cherry-5x9xx?file=/src/App.js
You can also use Context but I believe this is a simpler approach.
Answered By - Mosh Feu
Answer Checked By - - Marie Seifert (ReactFix Admin)