here i need to control carousel slides with custom buttons i achieved this with the help of this Example.
This is the code for it..
import React, { useState } from "react";
import { Carousel, Button, Container, Row } from "react-bootstrap";
import Cards from "../cards";
import "./slider.css";
const Slider = () => {
const [index, setIndex] = useState(0);
const handleSelect = (selectedIndex, e) => {
setIndex(selectedIndex);
};
const onPrevClick = () => {
if (index > 0) {
setIndex(index - 1);
} else if (index === 0) setIndex(2);
};
const onNextClick = () => {
if (index === 2) {
setIndex(0);
} else if (index === 0 || index > 0) setIndex(index + 1);
};
return (
<>
<div className="button-container">
<Container>
<Row>
<Button variant="primary" onClick={onPrevClick}>
Previous
</Button>
<Button variant="primary" onClick={onNextClick}>
Next
</Button>
</Row>
</Container>
</div>
<Carousel
activeIndex={index}
onSelect={handleSelect}
indicators={false}
controls={false}
>
<Carousel.Item>
<Cards />
</Carousel.Item>
<Carousel.Item>
<Cards />
</Carousel.Item>
<Carousel.Item>
<Cards />
</Carousel.Item>
</Carousel>
</>
);
};
export default Slider;
But when the active index is in last item that means in the last carousel slide. When i press the next button, it moves all the way around from last carousel to first carousel, i.e the direction is like 3 -> 2 -> 1.
I know that something i made terrible logic in the onClick functions. So i am seeking help to suggest me the best way to control carousel slides with custom buttons , your help is much appreciated. Please review my code and tell me any suggestions. Thanks in advance.
And here is the code sandbox link
Using the ref property you can control the prev and next actions
const ref = useRef(null);
const onPrevClick = () => {
ref.current.prev();
};
const onNextClick = () => {
ref.current.next();
};
JSX
<Carousel
ref={ref}
...
>
Related
I am using Material UI accordion my issue is if I click on the arrow accordion will get open but again I click on the arrow it will not get closed I need to set it when the user clicks on the arrow according will close and open based on the arrow click check code sandbox link for better understanding.
export default function ControlledAccordions() {
const [expanded, setExpanded] = React.useState(false);
// const handleChange = (panel) => (event, isExpanded) => {
// setExpanded(isExpanded ? panel : false);
// };
const handleChange = (pannel) => {
setExpanded(pannel);
};
const panaalData = ["panel1", "panel2", "panel3", "panel4"];
return (
<div>
{panaalData.map((value, i) => {
return (
<Accordion expanded={expanded === `panel${i}`}>
<AccordionSummary
expandIcon={
<ExpandMoreIcon
onClick={() => {
handleChange(`panel${i}`);
}}
style={{ cursor: "pointer" }}
/>
}
aria-controls="panel1d-content"
id="panel1d-header"
>
fdsfdsf
</AccordionSummary>
<AccordionDetails>dfdf</AccordionDetails>
</Accordion>
);
})}
</div>
);
}
Code SandBox Link
you need to reset panel in that case. You can do that in change handler.
const handleChange = (pannel) => {
setExpanded(expended === pannel ? '' : pannel);
};
when you click the already expanded panel, it just sets it to be expanded again.
you need to check whether the clicked panel is already expanded and if so collapse it instead of expanding it:
const handleChange = (pannel) => {
if (expanded === pannel) setExpanded(false);
else setExpanded(pannel);
};
Create another component called MyAccordian and keep toggling accordion logic in that component. That way you don't need to handle toggling for each and every component separately.
export default function ControlledAccordions() {
const panaalData = ["panel1", "panel2", "panel3", "panel4"];
return (
<div>
{panaalData.map((value, i) => {
return <MyAccordian value={value} />;
})}
</div>
);
}
const MyAccordian = ({ value }) => {
const [expanded, setExpanded] = React.useState(false);
return (
<Accordion expanded={expanded}>
<AccordionSummary
expandIcon={
<ExpandMoreIcon
onClick={() => {
setExpanded((prev) => !prev);
}}
style={{ cursor: "pointer" }}
/>
}
aria-controls="panel1d-content"
id="panel1d-header"
>
{value}
</AccordionSummary>
<AccordionDetails>{value}</AccordionDetails>
</Accordion>
);
};
Working Demo
export default function ControlledAccordions() {
// initial state, everything is closed,
const [expandedIndex, setExpandedIndex] = React.useState(-1);
// this should be handleClic
const handleChange = (index) => {
// in useState, current expandedIndex is passed as the argument
// whatever we return will be set as the expandedIndex
setExpandedIndex((currentIndex) => {
// if any box is open, currentIndex will be that index
// when I click on the open box, it will set the expandedIndex=-1
if (currentIndex === index) {
return -1;
} else {
// If I reached here, that means I am on a closed box
// when I click I swithc the expandedIndex to current box's index
return index;
}
});
};
const panaalData = ["panel1", "panel2", "panel3", "panel4"];
return (
<div>
{panaalData.map((value, i) => {
// when handleChange runs on AccordionSummary expandedIndex===i
// that means when i click on the current box, it will be open
const isExpanded = expandedIndex === i;
return (
<Accordion expanded={isExpanded}>
<AccordionSummary
onClick={() => handleChange(i)}
expandIcon={
// I dont know #mui/material too much.
// main question is "I need to open and close accordion based on arrow click"
<ExpandMoreIcon
onClick={() => handleChange(i)}
style={{ cursor: "pointer" }}
/>
}
aria-controls="panel1d-content"
id="panel1d-header"
>
{value}
</AccordionSummary>
<AccordionDetails
style={{ backgroundColor: "green" }}
>{`box index ${i} is open`}</AccordionDetails>
</Accordion>
);
})}
</div>
);
}
proof of work:
const handleChange = (pannel) => {
setExpanded(!pannel);
};
I am a Beginner to Reactjs and I just started working on a Tinder Clone with swipe functionality using tinde-card-react.
I am trying to get two variables to update using React useState() but coudn't.
There are 2 main components inside the main function, a TinderCards component and Swipe right and left and Replay buttons. The problem is that when I swipe the cards manually variables don't get updated and this is not the case when i swipe using the buttons.
In the current log, I swiped the cards twice to the right and logged the variables alreadyRemoved and people. The variable people is initially an Array containing 3 objects so after the second swipe it's supposed to log only 2 objects not 3, While the alreadyRemoved variable is supposed to update to the missing elements of the variable people.
This is my code :
import React, { useState, useEffect, useMemo } from 'react';
import './IslamCards.css';
import Cards from 'react-tinder-card';
import database from './firebase';
import hate from "./Cross.png"
import replayb from "./Replay.png"
import love from "./Love.png"
import IconButton from "#material-ui/core/IconButton"
function IslamCards(props) {
let [people, setPeople] = useState([])
useEffect(() => {
database.collection("People").onSnapshot(snapshot => { setPeople(snapshot.docs.map(doc => doc.data())) })
}, [])
let [alreadyRemoved , setalreadyRemoved] = useState([])
let buttonClicked = "not clicked"
// This fixes issues with updating characters state forcing it to use the current state and not the state that was active when the card was created.
let childRefs = useMemo(() => Array(people.length).fill(0).map(() => React.createRef()), [people.length])
let swiped = () => {
if(buttonClicked!=="clicked"){
console.log("swiped but not clicked")
if(people.length){
let cardsLeft = people.filter(person => !alreadyRemoved.includes(person))
if (cardsLeft.length) {
let toBeRemoved = cardsLeft[cardsLeft.length - 1] // Find the card object to be removed
let index = people.map(person => person.name).indexOf(toBeRemoved.name)// Find the index of which to make the reference to
setalreadyRemoved(list => [...list, toBeRemoved])
setPeople(people.filter((_, personIndex) => personIndex !== index))
console.log(people)
console.log(alreadyRemoved)
}
}
buttonClicked="not clicked"
}
}
let swipe = (dir) => {
buttonClicked="clicked"
console.log("clicked but not swiped")
if(people.length){
let cardsLeft = people.filter(person => !alreadyRemoved.includes(person))
if (cardsLeft.length) {
let toBeRemoved = cardsLeft[cardsLeft.length - 1] // Find the card object to be removed
let index = people.map(person => person.name).indexOf(toBeRemoved.name)// Find the index of which to make the reference to
setalreadyRemoved(list => [...list, toBeRemoved])
childRefs[index].current.swipe(dir)
let timer =setTimeout(function () {
setPeople(people.filter((_, personIndex) => personIndex !== index))}
, 1000)
console.log(people)
console.log(alreadyRemoved)
}
// Swipe the card!
}
}
let replay = () => {
let cardsremoved = alreadyRemoved
console.log(cardsremoved)
if (cardsremoved.length) {
let toBeReset = cardsremoved[cardsremoved.length - 1] // Find the card object to be reset
console.log(toBeReset)
setalreadyRemoved(alreadyRemoved.filter((_, personIndex) => personIndex !== (alreadyRemoved.length-1)))
if (!alreadyRemoved.length===0){ alreadyRemoved=[]}
let newPeople = people.concat(toBeReset)
setPeople(newPeople)
// Make sure the next card gets removed next time if this card do not have time to exit the screen
}
}
return (
<div>
<div className="cardContainer">
{people.map((person, index) => {
return (
<Cards ref={childRefs[index]} onSwipe={swiped} className="swipe" key={index} preventSwipe={['up', 'down']}>
<div style={{ backgroundImage: `url(${person.url})` }} className="Cards">
<h3>{person.name}</h3>
</div>
</Cards>);
})}
</div>
<div className="reactionButtons">
<IconButton onClick={() => swipe('left')}>
<img id="hateButton" alt="d" src={hate} style={{ width: "10vh", marginBottom: "5vh", pointerEvents: "all" }} />
</IconButton>
<IconButton onClick={() => replay()}>
<img id="replayButton" alt="e" src={replayb} style={{ width: "11vh", marginBottom: "5vh", pointerEvents: "all" }} />
</IconButton>
<IconButton onClick={() => swipe('right')}>
<img id="loveButton" alt="f" src={love} style={{ width: "11vh", marginBottom: "5vh", pointerEvents: "all" }} />
</IconButton>
</div>
</div>
);
}
export default IslamCards;
My console Log :
UPDATE :
As suggested in the 1st answer, I removed the Timer from the swiped() function but the problem persisted.
I hope to get more suggestions, so that I can solve this problem.
I can see the problem, but you might need to figure out what to do after that.
setPeople(people.filter((_, personIndex) => personIndex !== index))}
, 1000)
The problem is that index is figured out from the current update, however it takes 1 second to reach the next update, in between, your index points to the same one, because your index is derived from the people.
Before y'all say global state(redux), I'd like to say one thing. I'm mapping through an array I fetched from my API. I receive images and map over them and render my Slider component. Every 2 sliders must share the same state. So, then if i move to the next slide in the first slider, then the second slider must also go to the next slide(but not any other slides). If I move to the next slide in the 5th slider, the 6th must also move to the next slide... so on.
Component where I map over slides:
<div className='image-grid'>
{screenshots.map((imagesByResolution, resIdx, screenshotResArr) => {
return imagesByResolution.map((img, scriptIdx, screenshotScriptsArr) => {
return <Slider slides={formattedSlides} />;
});
})}
</div>
Slider:
import Button from '#material-ui/core/Button';
import MobileStepper from '#material-ui/core/MobileStepper';
import { useTheme } from '#material-ui/core/styles';
import KeyboardArrowLeft from '#material-ui/icons/KeyboardArrowLeft';
import KeyboardArrowRight from '#material-ui/icons/KeyboardArrowRight';
import React from 'react';
import SwipeableViews from 'react-swipeable-views';
import { autoPlay } from 'react-swipeable-views-utils';
import { encodeImage } from '../services/images';
import useStyles from '../styles/slider';
const AutoPlaySwipeableViews = autoPlay(SwipeableViews);
export interface ISlide {
title: string;
img: ArrayBuffer;
}
interface Props {
slides: ISlide[];
}
export default function Slider(props: Props) {
console.log(props);
const { slides } = props;
const classes = useStyles();
const theme = useTheme();
const [activeSlide, setActiveSlide] = React.useState(0);
const maxSlides = slides.length;
const handleNext = () => {
setActiveSlide((prevActiveStep) => prevActiveStep + 1);
};
const handleBack = () => {
setActiveSlide((prevActiveStep) => prevActiveStep - 1);
};
const handleSlideChange = (step: number) => {
setActiveSlide(step);
};
return (
<div className={classes.root}>
<div className={classes.header}>
<h4 className={classes.title}>{slides[activeSlide].title}</h4>
</div>
<AutoPlaySwipeableViews
axis={theme.direction === 'rtl' ? 'x-reverse' : 'x'}
index={activeSlide}
onChangeIndex={handleSlideChange}
enableMouseEvents
>
{slides.map((slide, index) => (
<div key={index}>
{Math.abs(activeSlide - index) <= 2 ? (
<img className={classes.img} src={encodeImage(slide.img, 'image/png')} alt={slide.title} />
) : null}
</div>
))}
</AutoPlaySwipeableViews>
<MobileStepper
steps={maxSlides}
position='static'
variant='text'
activeStep={activeSlide}
nextButton={
<Button size='small' onClick={handleNext} disabled={activeSlide === maxSlides - 1}>
Next
{theme.direction === 'rtl' ? <KeyboardArrowLeft /> : <KeyboardArrowRight />}
</Button>
}
backButton={
<Button size='small' onClick={handleBack} disabled={activeSlide === 0}>
{theme.direction === 'rtl' ? <KeyboardArrowRight /> : <KeyboardArrowLeft />}
Back
</Button>
}
/>
</div>
);
}
If this is not possible using either some global state management library or plain ol' react state, what is the other alternative? Thanks in advance!
Pass a unique key prop to each instance of your component.
Credits: https://stackoverflow.com/a/65654818/9990676
I have two cards now when I hover on one card it triggers in all two cards on hover (onMouseEnter)
Here is the solutions I have
import React, { useState } from "react";
const Buttons = () => {
const [isShown, setIsShown] = useState(false);
return (
<div>
<div
onMouseEnter={() => setIsShown(true)}
onMouseLeave={() => setIsShown(false)}
className="wrapper-btn"
>
{isShown && <button> test 1 </button>}
</div>
<div
onMouseEnter={() => setIsShown(true)}
onMouseLeave={() => setIsShown(false)}
className="wrapper-btn"
>
{isShown && <button> test 2 </button>}
</div>
</div>
);
};
export default Buttons;
What is wrong here?
both share the same state, you can abstract your code to another component where each one has an independent state:
import React, { useState } from "react";
const Buttons = () => {
return (
<div>
<ButtonDisplay btnContent='test 1' />
<ButtonDisplay btnContent='test 2' />
</div>
);
};
export default Buttons;
const ButtonDisplay = ({ btnContent }) => {
const [isShown, setIsShown] = useState(false);
return (
<div
onMouseEnter={() => setIsShown(true)}
onMouseLeave={() => setIsShown(false)}
className="wrapper-btn"
>
{isShown && <button> { btnContent } </button>}
</div>
)}
this would be the approach I would take since keeps your code dry.
other approach possible would change isShown state to an array that tracks each button isShown state, where onMouseEnter|Leave would update a specific index of that array and also read from that one, hence you would render your button based on specific value from an index of your state. or you could create a state to each button which would be the least optimal when you have multiple buttons.
I have cards and modals, I need to only show 2 cards in the page and have a button to show the rest, I’m new in programming and react, I don’t know what I have to do, that’s what I have now,
import React from "react"
import { Container } from "./_styles"
import { useTheme } from "#material-ui/core/styles"
import ImgMediaCard from "./../../../components/Cartao"
import AlertDialog from './../../../components/Modal'
export default function PortfolioSection(props) {
let arrayProjetos = props.projects;
const [selectedId, setSelectedId] = React.useState(0);
const [open, setOpen] = React.useState(false);
const handleClickOpen = (id) => {
setSelectedId(id);
setOpen(true);
};
const handleClose = () => {
setOpen(false);
};
let projetos = arrayProjetos.edges[selectedId].node.frontmatter;
return (
<Container>
{arrayProjetos.edges.map(
function criaCard(e, index){
let title = e.node.frontmatter.name;
let imageCard = e.node.frontmatter.images[0];
return (
<>
<ImgMediaCard
alt={title}
imagetitle={title}
texttitle={title}
src={imageCard}
click={() => handleClickOpen(index)}>
</ImgMediaCard>
</>
)
}
)}
<AlertDialog
imageModal={projetos.images[1]}
open={open}
handleClose={handleClose}
title={projetos.name}
text={projetos.description} />
</Container>
)
}
I'm using hooks to open the right modal when I click the "See more" button in the card, its working ok, I have 6 cards now, but I can add more in the future. I just need to limit how many cards I see when I enter the page and have a button to show everything.
API: you can add a pagination and total properties to your api call which returns 2 cards by default and you can handle the count of cards by increasing the pagination value. You may notice to add check to avoid situations like count > total.
UI: you can add const [cardCount, setCardCount] = useState(2)
and map through your cards array until the index not greater than cardCount value:
{arrayProjetos.edges.map((e, index) => { return index <= cardCount && <ImgMediaCard ... /> })}
<Box display={cardCount === 2 ? 'none' : 'block'}>
<Button
onClick={()=> arrayProjetos.edges.length - cardCount === 3 ? setCardCount(...cardCount - 1) : setCardCount(...cardCount - 2)}>See less</Button>
</Box>
<Box display={cardCount === arrayProjetos.edges.length ? 'none' : 'block'} >
<Button
onClick={() => arrayProjetos.edges.length - cardCount === 1 ? setCardCount(...cardCount + 1) : setCardCount(...cardCount + 2)}>See more </Button>
</Box>
How are you getting the cards?
You need to lazy load, if you are getting from a server, you can implement pagination, so the server sends back 2 cards every time (or based on a page data you send to server)
So every time you click the "load more" button, you fire a function who ask server for two more cards, and add the response to your cards js variable
Thanks Nazar, I did something similar I guess:
const [showMore, setShowMore] = React.useState(false);
const clickShow = () => {
setShowMore(oldValue => !oldValue);
showMore ? setButtonText("Ver Mais >") : setButtonText("Ver Menos <");
};
arrayProjetos.edges.slice(0, showMore ? arrayProjetos.edges.lenght : 2).map(
function criaCard(e, index){/*same thing here*/})
<button onClick={() => clickShow()}>{buttonText}</button>
I used slice to limit the array and a hook to change value