I am currently stuck on creating this dropdown filter functionality in React. I have this array here that I have in a solutions.json file:
[
{"name": "Employer Branding", "id": "415"},
{"name": "Account Based Marketing", "id": "414"},
{"name": "Thought Leadership", "id": "413"}
]
I want to filter out the case studies 'solutions' that I have displaying dynamically through a REST API. I currently have it showing ALL case studies. This is my code:
<div className={classes.cardContainer}>
<ul className="ulGrid">
{caseStudies.map((result) => {
return (
<li className={classes.liGrid} key={result.id}>
<LayeredCard
href={`/case-studies/${result.slug}`}
key={result.id}
title={result.title.rendered}
icon={result.acf.icon}
subtitle={result.acf.case_study_pre_heading}
readMore="Read More"
></LayeredCard>
</li>
);
})}
</ul>
</div>
I want to be able to filter these case studies through their solutions category and make it a condition that if the "solutions" ids match then render filtering result.
API data from a case study
So far I have created a dropdown filter without it actually filtering in a dropdown.js file:
import { useState, useRef, useEffect } from "react";
export default function Dropdown({
options,
prompt,
value,
onChange,
id,
label,
}) {
const [open, setOpen] = useState(false);
const ref = useRef(null);
//tracking when the dropdown is clicked
useEffect(() => {
document.addEventListener("click", close); //when click happens we call the close function
return () => document.removeEventListener("click", close);
}, []);
// (&& logical AND operator), if our dropdown is open and we click outside of the dropdown than the e.target is the
// html document which will NOT be equal to ref.current (our dropdown) which will close the dropdown as a result
function close(e) {
setOpen(e && e.target === ref.current);
}
return (
<div className={classes.dropdown}>
<div className={classes.control} onClick={() => setOpen((prev) => !prev)}>
<div className={classes.selectedValue} ref={ref}>
{/* if there is a value clicked then that shows if not the prompt shows */}
{value ? value[label] : prompt}
</div>
<div
className={`${classes.arrow} ${open ? `${classes.open}` : null} `}
></div>
<div
className={`${classes.options} ${open ? `${classes.open}` : null} `}
>
{/* className={classes.arrow open ? classes.open} */}
{options.map((option) => (
//the selected value is indicated with a css style
<div
key={option[id]}
className={`${classes.option} ${
value === option ? `${classes.selected}` : null
} `}
onClick={() => {
onChange(option); //change this
setOpen(true); //when you click the option the dropdown then closes.
}}
>
{option[label]}
</div>
))}
</div>
</div>
</div>
);
}
I am calling the Dropdown function in my casestudies.js file like so:
import solutions from "../data/solutions.json";
import Dropdown from "../components/dropdown/filterDropdown/Dropdown";
<Dropdown
options={solutions}
prompt="Solutions"
id="id"
label="name"
value={value}
onChange={(val) => setValue(val)}
/>
<div className={classes.cardContainer}>
<ul className="ulGrid">
{caseStudies.map((result) => {
return (
<li className={classes.liGrid} key={result.id}>
<LayeredCard
href={`/case-studies/${result.slug}`}
key={result.id}
title={result.title.rendered}
icon={result.acf.icon}
subtitle={result.acf.case_study_pre_heading}
readMore="Read More"
></LayeredCard>
</li>
);
})}
</ul>
</div>
Not sure how to render the filtering result. Can anyone help?
Related
I have created a carousel which is a column of ten dates ,for this i am mapping dates by momentjs, Inside each of this column , i am mapping different time slots for morning ,afternoon and evening,
and i have a functionality that only shows first two time slots and then there is a show more button, by clicking on this button more time slots are appear,but whenver i am clicking on this button all of the columns time slots is appearing, i have to handle all the column button individually..
Thank You in adavance... :)
below is my code...
const [showMoreClicked, setShowMoreClicked] = useState(false);
const [showMoreAfternoon, setShowMoreAfternoon] = useState(false);
const [showMoreEvening, setShowMoreEvening] = useState(false);
const showMoreSlotsForMorning = (e) => {
e.preventDefault();
setMoreClicked(!showMoreClicked);
};
const showMoreSlotsForAfternoon = (e) => {
e.preventDefault();
setShowMoreAfternoon(!showMoreAfternoon);
};
const showMoreSlotsForEvening = (e) => {
e.preventDefault();
setShowMoreEvening(!showMoreEvening);
};
<Carousel responsive={responsive}>
{nexttendates.map((elem, dateIndex) => {
return (
<div>
<button key={dateIndex} className="nexttendates">
{elem}
</button>
<div className="appointment-timelots">
<div className="availableslots">
<div className="availableslot">
<img
src="../elements/doctorlist/doctorcard/sunrise.png"
alt=""
className="sunrise"
/>
Morning
</div>
</div>
</div>
{morningtime.map((elem, morInd, arr) => {
if (showMoreClicked == false) {
while (morInd == 0 || morInd == 1)
return (
<button key={morInd} className="appointtimes">
{elem}
</button>
);
} else {
return (
<button key={morInd} className="appointtimes">
{elem}
</button>
);
}
})}
<button
choseIndex={dateIndex}
onClick={showMoreSlotsForMorning}
className="appointtimes"
>
{showMoreClicked ? "Show Less" : "Show More"}
</button>
<img
src="../elements/doctorlist/doctorcard/sun.png"
alt=""
className="afternoon"
/>
Afternoon
{afternoontime.map((elem, aftInd) => {
if (showMoreAfternoon == false) {
while (aftInd == 0 || aftInd == 1)
return (
<button className="appointtimes">{elem}</button>
);
} else {
return (
<button className="appointtimes">{elem}</button>
);
}
})}
<button
choseIndex={dateIndex}
onClick={showMoreSlotsForAfternoon}
className="appointtimes"
>
{showMoreAfternoon ? "Show Less" : "Show More"}
</button>
<img
src="../elements/doctorlist/doctorcard/night-mode.png"
alt=""
className="evening"
/>
Evening
{eveningtime.map((elem, eveInd) => {
if (showMoreEvening == false) {
while (eveInd == 0 || eveInd == 1) {
return (
<button className="appointtimes">{elem}</button>
);
}
} else {
return (
<button className="appointtimes">{elem}</button>
);
}
})}
<button
choseIndex={dateIndex}
onClick={showMoreSlotsForEvening}
className="appointtimes"
>
{showMoreEvening ? "Show Less" : "Show More"}
</button>
</div>
);
})}
</Carousel>
i think its happening because of i have mapped an array and only used one useState to check open or not...Can anybody plz help me....
Make the time slots list as a separate component, so that each of the morning, afternoon, and evening list will have their own state automatically for toggling display.
Something like this example:
import { useState } from "react";
// Toggle showMore value on click
const SlotsList = ({ slots }) => {
const [showMore, setShowMore] = useState(false);
const handleShowMoreClick = () => {
setShowMore((prev) => !prev);
};
// Filter the slots prop before map it if showMore is false
return (
<div>
{slots
.filter((elem, index) => (showMore ? true : index <= 1))
.map((elem, index) => (
<button key={index} className="appointtimes">
{elem}
</button>
))}
<button onClick={handleShowMoreClick} className="appointtimes">
{showMore ? "Show Less" : "Show More"}
</button>
</div>
);
};
export default SlotsList;
In this example, the list is filtered before being mapped out for an easier solution. The key property should be replaced by a unique ID to avoid conflict.
It can then be imported and used like below in the main component. Also reusable for all 3 lists, and each have separate display toggle.
<SlotsList slots={morningtime} />
how I can tigress an function depends on id of an element. Right now all elements getting clicked if I click on any single element. how to prevent to show all element ? here is my code
const[showsubcat,setShowSubCat] = useState(false)
let subcategory=(()=>{
setShowSubCat(prev=>!prev)
})
my jsx
{data.map((data)=>{
return(
<>
<li class="list-group-item" id={data.id} onClick={subcategory} >{data.main_category}</li>
{showsubcat &&
<li><i class="las la-angle-right" id="sub_category"></i> {data.sub_category}</li>
}
</>
)
see the screenshot. I am clicking on single items but it's showing all items.
Every li should have it own state
so it's either you create states based on number of li if they're just 2 elements max! but it's ugly and when you want to add more li it's gonna be a mess
so you just create a component defining the ListItem and every component has it own state.
function ListItem({data}) {
const[showsubcat,setShowSubCat] = useState(false)
const subcategory= ()=> setShowSubCat(prev=>!prev)
return (
<>
<li class="list-group-item" id={data.id} onClick={subcategory} >
{data.main_category}
</li>
{showsubcat &&
<li>
<i class="las la-angle-right" id="sub_category"></i>
{data.sub_category}
</li>
}
</>
)
}
and you use it in the list component like this
data.map((datum, index) => <ListItem key={index} data={datum} />
EDIT AFTER THE POST UPDATE (misunderstanding)
the list item (or the block containing the li and the helper text) should be an independant component to manage it own state
function PostAds(data) => {
return (
<>
{
data.map((data, index) => <ListItem key={index} data {data}/>
}
</>
)
}
function ListItem({data}) {
const [showsubcat, setShowSubCat] = useState(false)
const subcategory = () => setShowSubCat(prev => !prev)
return (
<>
<li
class="list-group-item"
id={data.id}
onClick={subcategory}>
{data.main_category}
</li>
{
showsubcat &&
<li >
<i class = "las la-angle-right" id = "sub_category"></i>
{data.sub_category}
</li>
}
</>
)
}
The reason this is happening is because you are using the same variable showsubcat to check if the category was clicked or not.
A proper way to do this would be by either making showsubcat as an array that holds ids of those categories that were clicked like:
const[showsubcat,setShowSubCat] = useState([])
let subcategory=((categoryId)=>
showsubcat.includes(categoryId) ?
setShowSubCat(showsubcat.filter(el => el !== categoryId)) :
setShowSubCat([...showsubcat, categoryId]));
and then while mapping the data:
{data.map((category)=>
(
<>
<li class="list-group-item" id={category.id} key={category.id}
onClick={() => subcategory(category.id)}>
{category.main_category}
</li>
{showsubcat.includes(category.id) &&
<li>
<i class="las la-angle-right"
id="sub_category" key={`subCategory${category.id}`} />
{category.sub_category}
</li>
}
</>
)
}
The other method would be to add a new key in your data array as selectedCategory and change its value to true/false based on the click, but this is a bit lengthy, let me know if you still want to know that process.
Also, accept the answer if it helps!
This should be easy but of course I can't figure it out :)
Basically I've implemented a "load more" button when there are more items to display from array but when it reaches the end, I'd like to hide it. The code below doesn't work and I have a feeling it's because it need to check the index of the array and if it's at the end, then hide the button but I am unsure of how to go about the syntax. Thanks
import { useState } from 'react'
import releases from "../data/releases.json";
import styled from 'styled-components';
import { Link } from 'react-router-dom';
import '../main.css'
const Release = () => {
// get first 12 items in array & load next 12 with "see more button"
const STEP = 12;
const [items, setItems] = useState(releases.slice(0, STEP));
const loadMore = () => {
setItems([...items, ...releases.slice(items.length, items.length + STEP)]);
};
return (
<Wrapper>
<div className="release fadein">
{items.map((item, i) => (
<div className="item" key={i}>
<Link to={`/release/${item.id}`}>
<img src={item.imageURL} alt={item.artist} />
</Link>
</div>
))}
{/* code that isn't working here */}
{items ? <button onClick={loadMore} className="btn" > show more </button> : <button className="btn hidden" ></button>}
</div>
</Wrapper >
)
}
when the items are less than 12 the button will not be displayed
{items%12 ===0 ? <button onClick={loadMore} className="btn" > show more </button> : nul}
I am making a Accordion and when we click each individual item then its opening or closing well.
Now I have implemented expand all or collapse all option to that to make all the accordions expand/collapse.
Accordion.js
const accordionArray = [
{ heading: "Heading 1", text: "Text for Heading 1" },
{ heading: "Heading 2", text: "Text for Heading 2" },
{ heading: "Heading 3", text: "Text for Heading 3" }
];
.
.
.
{accordionArray.map((item, index) => (
<div key={index}>
<Accordion>
<Heading>
<div className="heading-box">
<h1 className="heading">{item.heading}</h1>
</div>
</Heading>
<Text expandAll={expandAll}>
<p className="text">{item.text}</p>
</Text>
</Accordion>
</div>
))}
And text.js is a file where I am making the action to open any particular content of the accordion and the code as follows,
import React from "react";
class Text extends React.Component {
render() {
return (
<div style={{ ...this.props.style }}>
{this.props.expandAll ? (
<div className={`content open`}>
{this.props.render && this.props.render(this.props.text)}
</div>
) : (
<div className={`content ${this.props.text ? "open" : ""}`}>
{this.props.text ? this.props.children : ""}
{this.props.text
? this.props.render && this.props.render(this.props.text)
: ""}
</div>
)}
</div>
);
}
}
export default Text;
Here via this.props.expandAll I am getting the value whether the expandAll is true or false. If it is true then all accordion will get the class className={`content open`} so all will gets opened.
Problem:
The open class is applied but the inside text content is not rendered.
So this line doesn't work,
{this.props.render && this.props.render(this.props.text)}
Requirement:
If expand all/collapse all button is clicked then all the accordions should gets opened/closed respectively.
This should work irrespective of previously opened/closed accordion.. So if Expand all then it should open all the accordion or else needs to close all accordion even though it was opened/closed previously.
Links:
This is the link of the file https://codesandbox.io/s/react-accordion-forked-sm5fw?file=/src/GetAccordion.js where the props are actually gets passed down.
Edit:
If I use {this.props.children} then every accordion gets opened.. No issues.
But if I open any accordion manually on click over particular item then If i click expand all then its expanded(expected) but If I click back Collapse all option then not all the accordions are closed.. The ones which we opened previously are still in open state.. But expected behavior here is that everything should gets closed.
In your file text.js
at line number 9. please replace the previous code by:
{this.props.children}
Tried in the sandbox and worked for me.
///
cant add a comment so editing the answer itself.
Accordian.js contains your hook expandAll and the heading boolean is already happening GetAccordian.js.
I suggest moving the expand all to GetAccordian.js so that you can control both values.
in this case this.props.render is not a function and this.props.text is undefined, try replacing this line
<div className={`content open`}>
{this.props.render && this.props.render(this.props.text)}
</div>
by this:
<div className={`content open`}>
{this.props.children}
</div>
EDIT: //
Other solution is to pass the expandAll property to the Accordion component
<Accordion expandAll={expandAll}>
<Heading>
<div className="heading-box">
<h1 className="heading">{item.heading}</h1>
</div>
</Heading>
<Text>
<p className="text">{item.text}</p>
</Text>
</Accordion>
then in getAccordion.js
onShow = (i) => {
this.setState({
active: this.props.expandAll ? -1: i,
reserve: this.props.expandAll ? -1: i
});
if (this.state.reserve === i) {
this.setState({
active: -1,
reserve: -1
});
}
};
render() {
const children = React.Children.map(this.props.children, (child, i) => {
return React.cloneElement(child, {
heading: this.props.expandAll || this.state.active === i,
text: this.props.expandAll || this.state.active + stage === i,
onShow: () => this.onShow(i)
});
});
return <div className="accordion">{children}</div>;
}
};
Building off of #lissettdm answer, it's not clear to me why getAccordion and accordion are two separate entities. You might have a very valid reason for the separation, but the fact that the two components' states are interdependent hints that they might be better implemented as one component.
Accordion now controls the state of it's children directly, as before, but without using getAccordion. Toggling expandAll now resets the states of the individual items as well.
const NormalAccordion = () => {
const accordionArray = [ //... your data ];
const [state, setState] = useState({
expandAll: false,
...accordionArray.map(item => false),
});
const handleExpandAll = () => {
setState((prevState) => ({
expandAll: !prevState.expandAll,
...accordionArray.map(item => !prevState.expandAll),
}));
};
const handleTextExpand = (id) => {
setState((prevState) => ({
...prevState,
[id]: !prevState[id]
}));
};
return (
<>
<div className="w-full text-right">
<button onClick={handleExpandAll}>
{state.expandAll ? `Collapse All` : `Expand All`}
</button>
</div>
<br />
{accordionArray.map((item, index) => (
<div key={index}>
<div className="accordion">
<Heading handleTextExpand={handleTextExpand} id={index}>
<div className="heading-box">
<h1 className="heading">{item.heading}</h1>
</div>
</Heading>
<Text shouldExpand={state[index]}>
<p className="text">{item.text}</p>
</Text>
</div>
</div>
))}
</>
);
};
Heading passes back the index so the parent component knows which item to turn off.
class Heading extends React.Component {
handleExpand = () => {
this.props.handleTextExpand(this.props.id);
};
render() {
return (
<div
style={ //... your styles}
onClick={this.handleExpand}
>
{this.props.children}
</div>
);
}
}
Text only cares about one prop to determine if it should display the expand content.
class Text extends React.Component {
render() {
return (
<div style={{ ...this.props.style }}>
<div
className={`content ${this.props.shouldExpand ? "open" : ""}`}
>
{this.props.shouldExpand ? this.props.children : ""}
</div>
</div>
);
}
}
I have built this modal component using react hooks. However the data that the modal shows when it pops up its incorrect (it always shows the name property for last element in the array).
//Modal.js
import ReactDOM from 'react-dom';
const Modal = ({ isShowing, hide, home_team }) => {return isShowing ? ReactDOM.createPortal(
<React.Fragment>
<div className="modal-overlay"/>
<div className="modal-wrapper">
<div className="modal">
<div className="modal-header">
<a>Home team: {home_team}</a>
<button type="button" className="modal-close-button" onClick={hide}>
</button>
</div>
</div>
</div>
</React.Fragment>, document.body
) : null;}
export default Modal;
// Main component
const League = ({ league, matches }) =>{
const {isShowing, toggle} = useModal();
return (
<Fragment>
<h2>{league}</h2>
{
matches.map((
{
match_id,
country_id,
home_team
}
) =>
{
return (
<div>
<p>{match_id}</p>
<button className="button-default" onClick={toggle}>Show Modal</button>
<a>{home_team}</a>
<Modal
isShowing={isShowing}
hide={toggle}
home_team={home_team}
/>
<p>{home_team}</p>
</div>
)
})
}
</Fragment>
)};
This is what matches data set looks like:
[{
match_id: "269568",
country_id:"22",
home_team: "Real Kings"
},
{
match_id: "269569",
country_id:"22",
home_team: "Steenberg United"
},
{
match_id: "269570",
country_id:"22",
home_team: "JDR Stars "
},
{
match_id: "269571",
country_id:"22",
home_team: "Pretoria U"
},
]
I am not sure whats going on because the data seems to be passed fine.
<p>{home_team}</p>
in the main component is showing everytime the expected property, however the Modal always shows the last home_team item in the array (e.g.Pretoria U)
you need to call useModal inside of the map function. otherwise you will open on toggle all Modals and the last one overlaps the others
const HomeTeam = ({ match_id, country_id, home_team }) => {
const {isShowing, toggle} = useModal();
return (
<div>
<p>{match_id}</p>
<button className="button-default" onClick={toggle}>Show Modal</button>
<a>{home_team}</a>
<Modal
isShowing={isShowing}
hide={toggle}
home_team={home_team}
/>
<p>{home_team}</p>
</div>
)
}
const League = ({ league, matches }) => (
<Fragment>
<h2>{league}</h2>
{ matches.map((match) => <Hometeam {...match} /> }
</Fragment>
);