export const MobileFAQ = ({ faqs = [], defaultOpen = false }) => {
const { isOn: open, toggle } = useToggle(defaultOpen);
const ToggleIcon = open ? MinusIcon24Gray : PlusIcon24Gray;
return (
<div className="p16">
<h4 className="section-text-5 mb16">Frequently asked questions</h4>
{faqs.map((u, index) => (
<>
<div className="faq-item" onClick={toggle}>
<span className="c-black-3 text-medium">{u.question}</span>
<div className="faq-toggle-icon-mobile"></div>
</div>
<Collapse in={open}>
<div className="faq-answer c-black-3 text-4">{u.ans}</div>
</Collapse>
{index !== faqs.length - 1 && (
<div
style={{ height: 1, width: '100%', backgroundColor: '#e6e6e6' }}
className="mt12 mb16"
/>
)}
</>
))}
</div>
);
};
I have created faq array which is showing question and answer on toggle but it get open every index which should be coming as index wise
Related
Currently I have a map function that render a serial of image, and I realized that they share the same hover state, which means they will perform the same action when hovered. Is there are any standard practice to map duplicate components while assigning them unique/individual properies?
{itemData.map((item) => (
<ImageListItem key={item.img}>
<img
src={item.img}
alt={item.title}
loading="lazy"
onMouseOver={() => {setHover(true)}}
onMouseOut={() => {setHover(false)}}
style={{ transform: hover ? 'scale(1.5, 1.5)' : null }}
/>
<ImageListItemBar
title={item.title}
subtitle={item.author}
actionIcon={
<IconButton
sx={{ color: 'rgba(255, 255, 255, 0.54)' }}
aria-label={`info about ${item.title}`}
>
<InfoIcon />
</IconButton>
}
/>
You should use a component, which create a unique state for each element, i wrote an easy to understand example.
import React, { useState } from "react"
const items = [
{
title: 'Card1',
price: 100
},
{
title: 'Card2',
price: 50
},
{
title: 'Card3',
price: 200
},
]
export default function App() {
return (
<>
{
items.map(element => {
return(
<Card {...element}/>
)
})
}
</>
)
}
function Card({title, price, key}) {
const [isHovered, setHover] = useState(false)
return (
<>
<div
key={key}
onMouseOver={() => {setHover(true)}}
onMouseOut={() => {setHover(false)}}
>
<div>
{title}
</div>
<h3>
{
isHovered && price
}
</h3>
</div>
</>
);
}
I made the card price to show if hovered so you can see it works on each individual component.
Code sandbox if you want to check it out.
To provide unique properties, you need to have something that uniquely identifies your image component and use it to manage your state. In your case, your state hover should be an array or an object, not a boolean. Since you are using item.img as a key, I assume it is unique and hence it can help in your state management like this:
const [hover, setHover] = useState({});
{itemData.map((item) => (
<ImageListItem key={item.img}>
<img
src={item.img}
alt={item.title}
loading="lazy"
onMouseOver={() => setHover({...hover, [item.img]: true})}
onMouseOut={() => setHover({...hover, [item.img]: false})}
style={{ transform: hover ? 'scale(1.5, 1.5)' : null }}
/>
<ImageListItemBar
title={item.title}
subtitle={item.author}
actionIcon={
<IconButton
sx={{ color: 'rgba(255, 255, 255, 0.54)' }}
aria-label={`info about ${item.title}`}
>
<InfoIcon />
</IconButton>
}
/>
))
}
If you want the state to be in the parent without going all the way to an array or object, you can use a number instead. If only one item at a time is going to be active, you can just use the index of the active item as the state:
const { useState } = React;
const things = ["foo", "bar", "baz"];
function Component() {
const [active, setActive] = useState(-1);
const updateActivity = (index) => setActive(index === active ? -1 : index);
return (
<ul>
{things.map((thing, index) => (
<li>
<button key={index} onClick={() => updateActivity(index)}>
{index === active
? <strong>{thing}</strong>
: thing}
</button>
</li>
))}
<li>Value: {active}</li>
</ul>
);
}
ReactDOM.render(
<Component />,
document.getElementById("react")
);
<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="react"></div>
Alternatively, in cases where you want multiple items to be simultaneously active, you can use a "bit flag" approach where each bit of the value represents whether or not the corresponding index is active:
const { useState } = React;
const things = ["foo", "bar", "baz"];
function Component() {
const [active, setActive] = useState(0);
const updateActivity = (index) => setActive(active ^ Math.pow(2, index));
return (
<ul>
{things.map((thing, index) => (
<li>
<button key={index} onClick={() => updateActivity(index)}>
{active & Math.pow(2, index)
? <strong>{thing}</strong>
: thing}
</button>
</li>
))}
<li>Value: {active} ({active.toString(2).padStart(3, "0")})</li>
</ul>
);
}
ReactDOM.render(
<Component />,
document.getElementById("react2")
);
<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="react2"></div>
So I'm having this accordion component, which currently opens and close on click. But when I click on e.g. first item and it opens, when I click on second item the first close while I want to keep it open. Not sure how should I approach this.
const [activeTab, setActiveTab] = React.useState(props.tabIndex);
const tabs = props.tabs.map((tab) => {
return (
<>
<TabContainer>
<Tab
key={tab.index}
onClick= {() => {activTab == tab.index ? setActiveTab(-1) : setActiveTab(tab.index)})
className={typeof tab.content === 'string' ? '' : 'unactive-tab'}
>
{tab.name}
</Tab>
</TabContainer>
{tab.index === activeTab ?
<div
id="content"
style={{ width: '100%', margin: '2rem 0' }}
dangerouslySetInnerHTML={{ __html: tab.content as string }}
/>
: null}
</>
);
});
The best approach is to separate the Accordion item in its own component based on what you want and each of them would be able to have a "opened" state.
I won't give you a full solution, but I would do something similar to this:
const AccordionItem = (props) => {
// Each item has it's own state.
const [isOpened, setIsOpened] = React.useState(false);
return (YOUR COMPONENT);
};
And in your main component, you just do this:
const tabs = props.tabz.map((tab) => {
return (
<AccordionItem {...tab} /≥
);
});
change props.tabIndex to be an array of tabs indexes rather than just 1 index.
Assuming you now send an array of indexes through props. e.g
// props.tabIndexes = [1,5,2]
const [activeTabs, setActiveTabs] = React.useState(props.tabIndexes);
const tabs = props.tabs.map((tab) => {
return (
<>
<TabContainer>
<Tab
key={tab.index}
onClick= {() => {activeTabs.includes(tab.index) ? setActiveTabs(activeTabs.filter(tabIndex => tabIndex !== tab.index ) ) : setActiveTabs(activeTabs.push(tab.index)})
className={typeof tab.content === 'string' ? '' : 'unactive-tab'}
>
{tab.name}
</Tab>
</TabContainer>
{activeTabs.includes(tab.index) ?
<div
id="content"
style={{ width: '100%', margin: '2rem 0' }}
dangerouslySetInnerHTML={{ __html: tab.content as string }}
/>
: null}
</>
);
});
I have been using Algolia for React and I am having issues with its hierarchical Menu. When I select the third level of category from the menu everything else disappears from the screen and only the category and its parent category remains there.
Before clicking on the third level category
After clicking on the third level category. ( All other categories are gone )
This is my code for getting the categories and showing them on screen.
const HierarchicalMenu = ({ items, refine, createURL }) => {
const classes = useStyles();
console.log(items);
return (
<ul style={{ marginBottom: 0, paddingLeft: 20, marginLeft: 0 }}>
{items.map((item) => {
console.log(item.label);
console.log(item?.items ?? []);
return (
<li className={classes.categoryTitle} key={item.label}>
<a
href={createURL(item.value)}
className={cx(classes.categoryItem, item.isRefined ? 'selected' : null)}
onClick={(event) => {
event.preventDefault();
refine(item.value);
}}
>
{item.isRefined && item.items && <ArrowDropUpIcon />}
{!item.isRefined && <ArrowDropDownIcon />}
{item.label} <span>({item.count})</span>
</a>
{item.items && (
<HierarchicalMenu items={item.items} refine={refine} createURL={createURL} />
)}
</li>
);
})}
</ul>
);
};
const CustomHierarchicalMenu = connectHierarchicalMenu(HierarchicalMenu);
const CategoryFilter = () => {
return (
<CustomHierarchicalMenu attributes={['categories.lvl0', 'categories.lvl1', 'categories.lvl2']} />
)
}
This is how I have created the categories node in Alglolia
I'm trying to implement react-motion's TransitionMotion wrapper and made it to the home stretch but there's one more issue. In this example the interpolated -array consists of two elements (because chartConfigs.length is currently 2) and I've nested another map inside the first one. Everything else works fine except I obviously get two rows when I only want one. How to go around this in a clean way?
const getStyles = () => {
return chartConfigs.map(datum => ({
data: datum,
style: {
opacity: spring(1, { stiffness: 30})
},
key: datum.name
}))
}
const getDefaultStyles = () => {
return chartConfigs.map(datum =>({
data: datum,
style: {
opacity: 0
},
key: datum.name
}))
}
return (
<TransitionMotion
defaultStyles={getDefaultStyles()}
styles={getStyles()}
>
{(interpolated) => (
<div>
{interpolated.map((config) => (
<div key={config.key} style={{ ...config.style }}>
<div className='row' style={{ paddingTop: "30px" }}>
{chartConfigs.length > 1 &&
chartConfigs.map((chartConfig, i) => {
return (
<div
className={`col-lg-${columnsCount}`}
key={"chart-toggler" + i}
>
<div className='card m-b-30'>
<h4 className='card-title font-16 mt-0'>
{chartConfig.name}
</h4>
</div>
</div>
)
})}
</div>
</div>
))}
</div>
)}
</TransitionMotion>
)
EDIT:
Here's the new version of my solution but with the struggle of displaying elements next to each other on the row:
<div className='row' style={{ paddingTop: "30px" }}>
{chartConfigs.length > 1 ?
<TransitionMotion
defaultStyles={getDefaultStyles()}
styles={getStyles()}
willEnter={willEnter}
willLeave={willLeave}
>
{interpolated => (
<div id='container' style={{width: '100%', display: 'inline-block'}} >
{interpolated.map((config, i) => (
<div key={config.key} style={{ ...config.style }}>
{(selected = config.data.name === currentChartName)}
<div
className={`col-lg-${columnsCount}`}
key={"chart-toggler" + i}
>
<div
className={
selected
? "card m-b-30 text-white bg-primary"
: "card m-b-30"
}
style={{
width: '100%',
height: "calc(100% - 30px)",
}}
onClick={() => setCurrentChartName(config.data.name)}
>
<div className='card-body'>
<h4 className='card-title font-16 mt-0'>
{config.data.name}
</h4>
</div>
</div>
</div>
</div>
))}
</div>
)}
</TransitionMotion>
: null }
</div>
Additionally, I'm having trouble understanding how to use TransitionMotion when component unmounts. So basically the fade out effect when I render a different component on the page. Can I use the willLeave() function for this? Currently this is what it looks like but I don't know how to take it further:
const willLeave = () => ({
opacity: spring(0)
})
Thanks for your time and help!
TransitionMotion intentionally gives you more than the number of rows you’re currently rendering, since it remembers all the rows that are in the process of animating out.
So it depends on what you’re trying to achieve here. My hunch is that you’re probably misused chatConfigs in the inner level. You should be accessing config.data.name instead of chartConfig.name, no? You know what I mean?
I trying to add new button which would add new tabs
I'm using react-tabs
which build tabs like this
<Tabs>
<TabList>
<Tab>Title 1</Tab>
<Tab>Title 2</Tab>
</TabList>
<TabPanel>
<h2>Any content 1</h2>
</TabPanel>
<TabPanel>
<h2>Any content 2</h2>
</TabPanel>
</Tabs>
so I need two loop one for the tab and another one for tabpanel
like this
<Fragment>
<Tabs>
<TabList>
{stats.map(({ figure = "", instructions = "" }, i) => {
<Tab>
<RichText
tagName="h2"
placeholder={__("Write Recipe title…")}
value={figure}
onChange={value => updateStatProp(i, "figure", value[0])}
/>
</Tab>;
})}
</TabList>
{stats.map(({ figure = "", instructions = "" }, i) => {
<TabPanel>
<RichText
tagName="div"
multiline="p"
className="steps"
placeholder={__("Write the instructions…")}
value={instructions}
onChange={value => updateStatProp(i, "instructions", value[0])}
/>
<Button
isLarge={true}
onClick={() => {
const newStats = _cloneDeep(stats);
newStats.splice(i, 1);
setAttributes({ stats: newStats });
}}
>
<Dashicon icon="no-alt" />
</Button>
</TabPanel>;
})}
</Tabs>
<div style={{ textAlign: "center", padding: "8px 0" }}>
{stats.length < 5 && (
<Button
isLarge={true}
onClick={() => {
const newStats = _cloneDeep(stats);
newStats.push({ figure: "", instructions: "" });
setAttributes({ stats: newStats });
}}
>
Add new stat
</Button>
)}
</div>
</Fragment>
The state is stats.
Each item in the stats array looks something like this { figure: '100k', instructions:'humans'}
The button "add new stat" just appends a new stat object to this array and calls setAttributes.
The remove button just removes the item at that index.
It doesn't give any errors but there isn't any tab added when I click on add new stat button
You are not returning anything from the function given to map. Either return it or change the function body ({}) to parens (()) to make the return implicit:
<Fragment>
<Tabs>
<TabList>
{stats.map(({ figure = "", instructions = "" }, i) => { // return statement
return <Tab>
<RichText
tagName="h2"
placeholder={__("Write Recipe title…")}
value={figure}
onChange={value => updateStatProp(i, "figure", value[0])}
/>
</Tab>;
})}
</TabList>
{stats.map(({ figure = "", instructions = "" }, i) => ( // implicit return
<TabPanel>
<RichText
tagName="div"
multiline="p"
className="steps"
placeholder={__("Write the instructions…")}
value={instructions}
onChange={value => updateStatProp(i, "instructions", value[0])}
/>
<Button
isLarge={true}
onClick={() => {
const newStats = _cloneDeep(stats);
newStats.splice(i, 1);
setAttributes({ stats: newStats });
}}
>
<Dashicon icon="no-alt" />
</Button>
</TabPanel>;
))}
</Tabs>
<div style={{ textAlign: "center", padding: "8px 0" }}>
{stats.length < 5 && (
<Button
isLarge={true}
onClick={() => {
const newStats = _cloneDeep(stats);
newStats.push({ figure: "", instructions: "" });
setAttributes({ stats: newStats });
}}
>
Add new stat
</Button>
)}
</div>
</Fragment>