I'm creating an Accordion with react spring, the accordion is ready and working fine, but I can not make it to allow the accordion to open one item at the time, right now open all the three item in the same time, I wish to be able to open only one.
here is the codesand -> https://codesandbox.io/s/prod-sun-ttix7?file=/src/App.tsx
You just have to pull up the open state into the App component and and use the key or index as (open) indicator, like this:
App component
function App() {
const [openKey, setOpenKey] = useState()
const handleToggle = key => {
setOpenKey(openKey !== key ? key : null)
}
return (
<Container>
{data &&
data.map(({ name, content }) => (
<ServiceItem
key={name}
name={name}
content={content}
toggle={handleToggle}
open={openKey === name}
/>
))}
</Container>
);
}
ServiceItem component
const ServiceItem = ({ name, content, toggle, open }: ServiceItemProps): JSX.Element => {
return (
<div key={name}>
<Item onClick={() => toggle(name)}>
<Text className="text-15 regular laptop:text-20 laptop:regular">
{name}
</Text>
<Icon className="text-15 regular laptop:text-20 laptop:regular">
{!open ? "+" : "-"}
</Icon>
</Item>
<Expandable open={open}>
<ContentContainer>
<React.Fragment key={name}>
<Value>{content}</Value>
</React.Fragment>
</ContentContainer>
</Expandable>
</div>
);
};
Here is a working example: https://codesandbox.io/s/infallible-banzai-fnncw.
Related
How to pass active state, when a button is clicked in below React component?
I have a component:
<MyLinks links={links}/>
Where I pass this array: const links: CopyType[] = ['link', 'embed'];
// MyLinks component:
const [copyLinkType, setCopyLinkType] = useState<CopyType>();
return (
<React.Fragment>
<ul>
{links.map((linkType) => (
<TabIcon
type={linkType}
onClick={() => {
setCopyLinkType(linkType);
}}
/>
))}
</ul>
{copyLinkType && <TabPanel type={copyLinkType} />}
</React.Fragment>
);
In the frontend you get 2 buttons which show/hide the <TabPanel /> with it's associated content.
How to get/pass an active state when a button is clicked?
I tried passing isActive state as a prop through <TabIcon isActive={isActive} /> and then on the onClick handler setIsActive(true); but this will pass active state to both buttons in the same time?
Try to use refs. Something like this (w/o typescript codepen):
const TabIcon = (props) => {
const {activeRefs, onClick, type, index} = props
return (
<a onClick={()=>{
// On each click change value to the opposite. Or add your
// logic here
activeRefs.current[index] = !activeRefs.current[index]
onClick()
}}>
{type}
</a>
)
}
const MyLinks = (props) => {
const [copyLinkType, setCopyLinkType] = React.useState();
// Array which holds clicked link state like activeRefs.current[index]
const activeRefs = React.useRef([]);
const links = ['link1', 'link2'];
console.log({activeRefs})
return (
<ul>
{links.map((linkType, index) => (
<TabIcon
index={index}
activeRefs={activeRefs}
type={linkType}
onClick={() => {
setCopyLinkType(linkType);
}}
/>
))}
</ul>
);
}
ReactDOM.render(
<MyLinks />,
document.getElementById('root')
);
Created RenderCard component which creates multiple cards for given data.
I am creating bulk selection of card means selecting multiple cards. So when user click on circle present at the top left then that circle become check circle
So here the issue is that all card gets selected when user click on any card. So I want only that card select which users click
const Card = () => {
const [cardSelect, setCardSelect] = useState(false)
const onMouseEnter = () => {
console.log("onMouseEnter1")
}
const onMouseLeave = () => {
console.log("onMouseLeave1")
}
const logMessage = () => {
setCardSelect(prevCheck => !prevCheck);
}
const RenderCard = () => {
return album.map(item => {
return (
<Col className='container' onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave} key={item.title} md='2'>
<Card>
<div>
<Link to={`/pages/blog/detail/${item.id}`}>
<CardImg className='img-fluid image' src={item.img} alt={item.title} top />
</Link>
</div>
<div className='select-card'>
{cardSelect ? <CheckCircle onClick={logMessage} /> : <Circle onClick={logMessage} />}
</div>
<CardBody>
<CardTitle className="image2 te" >
<b>{item.title}</b>
</CardTitle>
<CardText>{item.comment} Comments</CardText>
</CardBody>
</Card>
</Col>
)
})
}
return (
<>
<Row>
<RenderCard />
</Row>
</>
)
}
Anyone can point me in the right direction where I am doing mistakes.
Thanks
If i understand you correctly i would suggest that
you should wrap anything that is inside the return statement and refactor it to its own component, and then pass item as prop in that component and make the clickhandler inside that component. like this:
const RenderCard = () => {
return album.map(item => {
return ( <Component item={item} />)
}
Now the component have its own state that shows either its clicked or not clicked
With this line of code :
cardSelect ? <CheckCircle onClick={logMessage} /> : <Circle onClick={logMessage} />
You are saying : "If cardSelect is true, then render CheckCircle component, otherwise, render Circle component and do that for all items in album list". So when the state changes, it will render only one type of object CheckCircles, or Circles as many times as there are elements in the album list for which you are calling the map method.
That is why you are seeing all checked circles when you set your variable cardSelect to true.
I use inView in my component. Until the posts were loaded, the user see the skeleton. But when I press delete button, the skeleton appears and dissappers. Can I stop this quick appearance when press the delete button.
class MyPosts extends Component {
deleteItem = (key) => {
this.props.deleteItem(key);
};
render() {
const { myPosts, classes } = this.props;
let posts = myPosts.map((item) => {
return (
<InView threshold={0}>
{({ ref, inView }) => (
<div ref={ref} inView={inView}>
{inView ? (
<Card>
<Typography >
{item.post}
</Typography>
<Button
onClick={() => this.deleteItem(item.key)}
>
Done
</Button>
</Card>
) : (
<Skeleton />
)}
</div>
)}
</InView>
);
});
return <div>{posts}</div>;
}
}
I have Select from antd styled with styled components with different icon. Everything works perfectly, except click on new icon inside of select doesn't trigger the dropdown to open or close.
<Styled.SortSelect
size={size ? size : 'large'}
defaultValue={defaultValue}
suffixIcon={<Styled.Icon />}
getPopupContainer={trigger => {
return trigger;
}}
>
{options.map((option: string) => {
return (
<Styled.SortOption className="custom-option" data-testid="sort-option" key={option} value={option}>
{option}
</Styled.SortOption>
);
})}
</Styled.SortSelect>
Simple demo
https://codesandbox.io/s/broken-arrow-click-nfpc7?file=/src/index.tsx
It seems to be a bug. Switching to an older version of antd e.g. 4.1.3 seems to solve your error
I think it's a bug. But in the meantime a workaround could be to handle whether the select is open yourself via an onClick on the icon like this:
export const SortSelect = ({ defaultValue, size, options }: Props) => {
const [open, setOpen] = useState(false);
return (
<Styled.SortSelect
size={size ? size : "large"}
defaultValue={defaultValue}
suffixIcon={<Styled.Icon onClick={() => setOpen(!open)} />}
open={open}
getPopupContainer={trigger => {
return trigger;
}}
>
{options.map((option: string) => {
return (
<Styled.SortOption
className="custom-option"
data-testid="sort-option"
key={option}
value={option}
>
{option}
</Styled.SortOption>
);
})}
</Styled.SortSelect>
);
};
My toggle is opening all wraps in the same time when i click in one specific, what's wrong?
const SomeData = ({ data, dayNumber }, props) => {
const Exams = () => {
const listExams = data.map((item) => (
<Fragment>
<Wrap key={item.id}>
<WrapCard userID={item.userID}>
<Button color="light" size="lg" block onClick={toggle} style={{ marginBottom: "1rem" }</Button>
<Collapse isOpen={isOpen}>
<Card></Card>
</Collapse>
</WrapCard>
</Wrap>
</Fragment>
));
return listExams;
};
const [isOpen, setIsOpen] = useState(false);
const toggle = () => setIsOpen(!isOpen);
};
Check out your rendered HTML. You'll see this code block for every item in data:
<Fragment>
<Wrap key="someID">
<WrapCard userID="someotherID">
<Button color="light" size="lg" block onClick={toggle} style={{ marginBottom: "1rem" }</Button>
<Collapse isOpen=!!---SAME VALUE HERE---!!
<Card></Card>
</Collapse>
</WrapCard>
</Wrap>
</Fragment>
(Some of the text above in your HTML will look different than what I typed, I only resolved some of it)
The problem is the ---SAME VALUE HERE--- I put in there. They all reference the same isOpen. So you need to have many values in your state, one for each item in data. I'm assuming they have a unique ID. so you could use a state that is an empty object, and when you toggle it on: setIsOpen({...isOpen, item.id: true}) and when you toggle it closed setIsOpen({...isOpen, item.id: false}). Then check for each item in data if it should be open. It may look like <Collapse isOpen={isOpen[item.id]}> which works only if you want it closed as the default state (if the item is undefined) as both undefined and false are falsy.
This would not work if you wanted the default state to be open. Then you'd have to <Collapse isOpen={typeof isOpen[item.id] === 'undefined' || isOpen[item.id] === true}>. There would also be more elegant optional chaining answers.
I suggest you to use Collapsible
Use Collapsible
npm install react-collapsible --save
yarn add react-collapsible
After Installing this modify your code like:
import Collapsible from 'react-collapsible';
const SomeData = ({ data, dayNumber }, props) => {
const Exams = () => {
const listExams = data.map((item) => (
<Fragment>
<Wrap key={item.id}>
<WrapCard userID={item.userID}>
<Collapsible trigger = {<span><Button color="light" size="lg" block onClick={toggle} style={{ marginBottom: "1rem" }</Button></span>}>
<Card></Card>
</Collapsible>
</WrapCard>
</Wrap>
</Fragment>
));
return listExams;
};
};