All my sidebar menu opens at once in ReactJS - reactjs

I am trying to create a sidebar menu with dropdwon
For I single menu it works fine but if I have multiple dropdown
All opens at one when I click on the menu to open the submenu.
I am getting the data for the menu from a json array
const SidebarItems = ({ items }) => {
const [open, setOpen] = useState(false)
return (
<div>
{items && items.map((item, index) => {
return (
<div key={index}>
<div>
<li >
<div className="nav-items" onClick={() => setOpen(!open)}>
<span>
<i className="bi-badge-vr" />
{item.title}
</span>
<i className="bi-chevron-down" />
</div>
</li>
</div>
<div className={open ? 'sub-menu sub-menu-open' : "sub-menu"} >
{item.chidren && item.chidren.map((sub_menu, sub_index) => {
return (
<div key={sub_menu}>
<Link to="manager/staff/create">
<span>
<i className="bi-badge-vr" />
{sub_menu.title}
</span>
</Link>
</div>
)
})}
</div>
</div>
)
})}
</div>
)
}

This is because they all use the same state.
Move the item to its own component so I can keep its own state:
const Item = ({ item }) => {
const [open, setOpen] = useState(false);
return <Your item code here />;
};
And in SideBarItems:
items.map((item, index) => {
return <Item item={item} key={index} />;
});

Related

TypeScript how to write this more pragmatic?

Can I write this code more pragmatic somehow (mainly the RenderMenu I want to improve, but suggestions on both is appreciated)? I can't really think of how. I'm not used to TypeScript. I'm rendering a food-menu from JSON (import {data} from './DinnerData'. First I render the title e.g. "Starters" then it goes through all the starters and rendering each dinner with two map functions.
export const RenderMenu = (props: Props) => {
return (
<>
{data.types.map((type, index) => {
return (
<>
<div className="row">
<div className="col-xl-12">
<div className="section-title text-center">
<h4 key={index}>
{type.name}
</h4>
</div>
</div>
</div>
<div className="row menu_style1">
{type.items.map((item, index) => {
return (
<>
<Dinner title={item.name} price={item.price} children={item.text} image={"https://i.imgur.com/kbpceNv.jpg"} />
</>
)
})}
</div>
</>
)
})}
</>
);
};
Here is the Menu itself.
export const Menu = (props: Props) => {
return (
<>
<section className="about-area pt-60 m-2 p-2" id="home">
<div className="container mb-5">
<div className="row">
<div className="col-xl-12 mb-60">
<div className="section-title text-center">
<p></p>
<h1>Vår meny</h1>
</div>
</div>
</div>
<RenderMenu/>
</div>
</section>
</>
);
};
You can extract smaller components.
MenuTitle
export const MenuTitle = (props) => {
const { name, key } = props;
return (
<div className="row">
<div className="col-xl-12">
<div className="section-title text-center">
<h4 key={key}>{name}</h4>
</div>
</div>
</div>
);
};
MenuItems
export const MenuItems = (props) => {
const { items } = props;
return (
<div className="row menu_style1">
{items.map((item, index) => {
<>
<Dinner
title={item.name}
price={item.price}
children={item.text}
image={"https://i.imgur.com/kbpceNv.jpg"}
/>
</>;
})}
</div>
);
};
RenderMenu is now more cleaner & easier on the head.
export const RenderMenu = (props) => {
return (
<>
{data.types.map((type, index) => {
return (
<>
<MenuTitle name={type.name} key={index} />
<MenuItems items={type.items} />
</>
);
})}
</>
);
};

Bootstrap carousel: sets active item to first when data changes (Reactjs)

My carousel with data.img is an array of link img
<div
id="carouselExampleControls"
className="codeinfo__carousel carousel slide"
data-bs-ride="carousel"
>
<div className="carousel-inner">
<div className="carousel-item active">
<img
src={data.img[0]}
className="codeinfo__img d-block w-100"
alt="..."
/>
</div>
{data.img.map(
(e, i) =>
i > 1 && (
<div className="carousel-item">
<img
src={e}
className="codeinfo__img d-block w-100"
alt="..."
/>
{i}
</div>
)
)}
</div>
<button
className="carousel-control-prev"
type="button"
data-bs-target="#carouselExampleControls"
data-bs-slide="prev"
>
<span className="carousel-control-prev-icon" aria-hidden="true" />
<span className="visually-hidden">Previous</span>
</button>
<button
className="carousel-control-next"
type="button"
data-bs-target="#carouselExampleControls"
data-bs-slide="next"
>
<span className="carousel-control-next-icon" aria-hidden="true" />
<span className="visually-hidden">Next</span>
</button>
</div>
When the data has changed, the active item does not return to the first item, but to the number of the previous active item. So if the length of the previous data.img is bigger than the length of the current data.img there will be nothing to display and the carousel will crash.(sorry for my english, I use google translate)
Like the example below. How to make class "active" return first item?
carousel1 > Change data > carousel2
I solved the problem myself. I removed all bootstrap actions and select active item using react code. I use useState to select active item, setInterval to autoPlay, useEffect to reset activity to 0. Here is my code:
const Carousel = (props) => {
const data = props.data;
const [nowActive, setNowActive] = useState(0);
const [onHover, setOnHover] = useState(false);
// autoplay (stop in hover)
useEffect(() => {
if (!onHover && data !== null) {
let i = nowActive;
const timer = setInterval(() => {
if (i >= data.img.length - 1) {
setNowActive(0);
i = 1;
} else {
setNowActive((prev) => prev + 1);
i = i + 1;
}
}, 3000);
return () => {
clearTimeout(timer);
};
}
}, [onHover, data]);
// change data >> return active = 0
useEffect(() => {
setNowActive(0);
}, [data]);
const carousel = () => {
return (
<div
id="carouselExampleControls"
className="codeinfo__carousel carousel slide"
data-bs-ride="carousel"
onMouseOver={() => setOnHover(true)}
onMouseLeave={() => setOnHover(false)}
>
<div className="carousel-inner">
{data.img.map((e, i) => (
<div className={"carousel-item " + (nowActive === i && "active")}>
<img src={e} className="codeinfo__img d-block w-100" alt="..." />
</div>
))}
</div>
<button
className="carousel-control-prev"
type="button"
onClick={() => {
nowActive === 0
? setNowActive(data.img.length - 1)
: setNowActive(nowActive - 1);
}}
>
<span className="carousel-control-prev-icon" aria-hidden="true" />
<span className="visually-hidden">Previous</span>
</button>
<button
className="carousel-control-next"
type="button"
onClick={() => {
nowActive === data.img.length - 1
? setNowActive(0)
: setNowActive(nowActive + 1);
}}
>
<span className="carousel-control-next-icon" aria-hidden="true" />
<span className="visually-hidden">Next</span>
</button>
</div>
);
};
return data !== null ?
<div>{carousel()}</div> :
<div>Null data</div>
Note: Although the problem has been solved, but this carousel does not have any Animation Effect when changing slides:
Carousel has a property called defaultActiveIndex, which is the initial value of activeIndex. You can use it to focus on the correct CarouselItem
Create a new State:
const [activeIndex, setActiveIndex] = useState(0);
Set the defaultActiveIndex to the newly created state:
Change the activeIndex whenever an item in the carousel is clicked:
onClick={(e) => { setActiveIndex(i); }}
Honestly they should just make this default behavior, not sure why we have to hack it.

onClick method called automatically react

I have a react component named <Filters /> which renders a component <PriceFilter />. The <PriceFilter /> component renders a <Filter /> component and some other JSX.
<Filters /> component:
const Filters = ({ price, setPrice }) => {
return (
<div>
<PriceFilter price={price} setPrice={setPrice} />
</div>
);
};
<PriceFilter /> component:
const PriceFilter = ({price, setPrice}) => {
const [show, setShow] = useState(false);
const toggleShow = () => setShow(!show);
return (
<div>
<Filter show={show} toggleShow={toggleShow}>
Price
</Filter>
{show && (
<FilterContainer>
<div>
<div onClick={() => setPrice('$')}>
<span>Inexpensive</span>
</div>
<div onClick={() => setPrice('$$')}>
<span>$$</span>
</div>
<div onClick={() => setPrice('$$$')}>
<span>$$$</span>
</div>
<div onClick={() => setPrice('$$$$')}>
<span>$$$$</span>
</div>
</div>
</FilterContainer>
)}
</div>
);
};
<Filter /> component:
const Filter = ({children, show, toggleShow}) => {
return (
<div>
<span>{children}</span>
{show ? <KeyboardArrowUpIcon onClick={toggleShow} /> : <KeyboardArrowDownIcon onClick={toggleShow} />}
</div>
);
};
Clicking on any of the options (Inexpensive/$$/$$$/$$$$) in the <PriceFilter /> component not only sets the state to the value passed ('$'/'$$'/'$$$'/'$$$$'), but it is also triggering the toggleShow function for some reason. Can someone please provide a solution to this so that the toggleShow function isn't called when clicking on the option.
The code works with minimal setup. See what is going wrong in higher level component Test where Filters is being rendered.
export const Test = () => {
const [price, setPrice] = useState('inexpensive')
return(
<Filters price={price} setPrice={setPrice} />
)
}
const Filters = ({ price, setPrice }) => {
return (
<div>
<PriceFilter price={price} setPrice={setPrice} />
</div>
);
};
const PriceFilter = ({price, setPrice}) => {
const [show, setShow] = useState(false);
const toggleShow = () => {
setShow(!show);
}
return (
<div>
<Filter show={show} toggleShow={toggleShow}>
{price}
</Filter>
{show && (
<div>
<div>
<div onClick={() => setPrice('$')}>
<span>Inexpensive</span>
</div>
<div onClick={() => setPrice('$$')}>
<span>$$</span>
</div>
<div onClick={() => setPrice('$$$')}>
<span>$$$</span>
</div>
<div onClick={() => setPrice('$$$$')}>
<span>$$$$</span>
</div>
</div>
</div>
)}
</div>
);
};
const Filter = ({children, show, toggleShow}) => {
return (
<div>
<span>{children}</span>
{show ? <div onClick={toggleShow}>up</div> : <div onClick={toggleShow}>down</div>}
</div>
);
};
It's working!! I didn't get why but there was a problem in the parent component. I had created a local component in the parent and rendered it there. I just extracted that local component in an independent component and it works now.

How to open one dropdown item?

friends, I have array of questions, and a dropdown list for them... i want to open any question, but all questions are opening together... please help
const FAQ = () => {
const [isOpenAnswer, setIsOpenAnswer] = useState(false)
const toggle = (id) => {
questions.forEach((q) => {
if(q.id === id){
setIsOpenAnswer((prevState) => !prevState)
}
})
}
return <Layout>
<div className="questionsBox pb-5">
<h2 className="title pt-4 pb-4" >Frequently Asked Questions</h2>
{questions.map((q, index) => {
return <div className="question pl-1 pt-3 pb-3 pr-1" key={index}>
<div className="d-flex justify-content-between">
<span className="questionTitle">{q.question}</span>
<img className="questionIcon"
src={Plus} alt="plus"
onClick={() => toggle(q.id)}
/>
</div>
{isOpenAnswer && <p className="answer pt-2 pb-2">
{q.answer}
{q.source}
</p>}
</div>
})}
</div>
</Layout>
}
Use a Javascript object to track which unique q.id is being set to true.
const FAQ = () => {
const [isOpenAnswer, setIsOpenAnswer] = useState({})
const toggle = (id) => {
setIsOpenAnswer(prevState => ({
...prevState,
[id]: !prevState[id],
});
}
return <Layout>
<div className="questionsBox pb-5">
<h2 className="title pt-4 pb-4" >Frequently Asked Questions</h2>
{questions.map((q, index) => {
return <div className="question pl-1 pt-3 pb-3 pr-1" key={index}>
<div className="d-flex justify-content-between">
<span className="questionTitle">{q.question}</span>
<img className="questionIcon"
src={Plus} alt="plus"
onClick={() => toggle(q.id)}
/>
</div>
{isOpenAnswer[q.id] && <p className="answer pt-2 pb-2">
{q.answer}
{q.source}
</p>}
</div>
})}
</div>
</Layout>
}
You're using the same prop for all of them here:
{isOpenAnswer && <p className="answer pt-2 pb-2">
{q.answer}
{q.source}
</p>}
Try saving something unique in state to identify what you're supposed to be showing, e.g.,
{selectedQuestionId && /* the rest */ }
and set the selectedQuestionId where you're currently setting isOpenAnswer .

How to open up only 1 panel onclick on React?

I have a button in each of the 3 panels. I am looking at a dropdown message in that one panel where I clicked the button. But currently, when I click on one of the buttons, all 3 panels will show the dropdown message. I read about making use of indexing but I am not exactly sure how to add it in. How can I go about solving this?
export default class CustomerDetails extends Component {
constructor(props) {
super(props);
this.state = {
listOpen: false,
};
}
// Toggle the dropdown menu
toggleList(name) {
this.setState(prevState => ({
listOpen: !prevState.listOpen
}))
}
render() {
const { listOpen } = this.state
if (!this.state.customerDetails)
return (<p>Loading Data</p>)
return (<div className="customerdetails">
<div className="addmargin">
<div className="col-md-9">
{this.state.customerDetails.data.map(customer => (
<Panel bsStyle="info" key={customer.name}>
<Panel.Heading>
<Panel.Title componentClass="h3">{customer.name}</Panel.Title>
</Panel.Heading>
<Panel.Body>
<img src={require(`./sampleimages/${customer.image}.jpg`)} className="Customer-image" alt="image" />
<br line-height="110%"></br>
<p align="left">{customer.desc}</p>
{/* Toggle dropdown menu */}
<div className="image-cropper">
<button><img src={arrow} className="arrow-button" onClick={() => this.toggleList(customer.name)} /></button>
{listOpen && <ul className="dd-list">
<li class="dropdown" className="dd-list-item" key={customer.name}>{customer.tip1}</li>
</ul>}
</div>
You can do it like this. In your state declare a variable which points to index of the panel you want to show as:
this.state = {
listOpen: 0,
};
Then modify your toogleList method as:
toggleList(index){
this.setState({ listOpen: index })
}
And finally, change your JSX as:
{this.state.customerDetails.data.map((customer, index) => (
<Panel bsStyle="info" key={customer.name}>
<Panel.Heading>
<Panel.Title componentClass="h3">{customer.name}</Panel.Title>
</Panel.Heading>
<Panel.Body>
<img src={require(`./sampleimages/${customer.image}.jpg`)} className="Customer-image" alt="image" />
<br line-height="110%"></br>
<p align="left">{customer.desc}</p>
{/* Toggle dropdown menu */}
<div className="image-cropper">
<button><img src={arrow} className="arrow-button" onClick={() => this.toggleList(index)} /></button>
{listOpen === index && <ul className="dd-list">
<li class="dropdown" className="dd-list-item" key={customer.name}>{customer.tip1}</li>
</ul>}
</div>
</PanelBody>
<Panel>
}
Hope this works for you.
// Toggle the dropdown menu
toggleList(index) {
let customerDetails = this.state.customerDetails
if (customerDetails.data[index].listOpen)
customerDetails.data[index].listOpen = false
else
customerDetails.data[index].listOpen = true
this.setState({ customerDetails })
}
change this function like this
{
this.state.customerDetails.data.map((customer, index) => (
<Panel bsStyle="info" key={customer.name}>
<Panel.Heading>
<Panel.Title componentClass="h3">{customer.name}</Panel.Title>
</Panel.Heading>
<Panel.Body>
<img src={require(`./sampleimages/${customer.image}.jpg`)} className="Customer-image" alt="image" />
<br line-height="110%"></br>
<p align="left">{customer.desc}</p>
{/* Toggle dropdown menu */}
<div className="image-cropper">
<button><img src={arrow} className="arrow-button" onClick={() => this.toggleList(index)} /></button>
{customer.listOpen && <ul className="dd-list">
<li class="dropdown" className="dd-list-item" key={customer.name}>{customer.tip1}</li>
</ul>}
</div>

Resources