How to force update current slide in Swiper? - reactjs

I have some images and slider with these images. I'm trying update current active slide with onClick event: (clicked image must be active slide)
In following code i've tried to change first-indexed image, but it doesn't work with Swiper, so I need force rerender my Swiper-slider, for the reason it started from first.
So the main goal is to make the clicked image the current slide
const [prev, setPrev] = useState()
const [ImageArray, setImageArray] = useState([item1, item2, item3])
const [slidesArray, setSlidesArray] = useState([item1, item2, item3])
const swiper = useSwiper()
const clickHandler = (image: any) => {
if (prev !== image) { //if clicked not the same
let index = slidesArray.indexOf(image) // find index of image in image array
let temp = [...slidesArray]
temp.splice(index, 1) //delete selected image
temp.unshift(image) // set first
setSlidesArray([...temp])
setPrev(image)
swiper.reInit() //not working
}
}
<div className={classes['Gallery__container']}>
{ImageArray.map((image, index) => (
<img src={image} alt="" key={index} width={200} height={200} onClick={() => clickHandler(image)}/>
))}
</div>
<Swiper
spaceBetween={50}
slidesPerView={'auto'}
loop
zoom={true}
>
{slidesArray.map((image,index) => (
<SwiperSlide key={index}>
<img src={image} alt="" width={200} height={200}/>
</SwiperSlide>
))}
</Swiper>
P.S. my code is working and first slide is changing, but I need to change the current

Related

An element is not removed from the array, how to fix it?

I have data that I get from api and through the map() method I display these "cards", each card has an image when clicked on which this image should receive an additional class. I implemented this by adding the index of the card to an array and now I can assign new classes to them, but I can't delete them
P.S. I have strict mode enabled, if it is disabled it removes extra classes on all other cards except the one I clicked on
//On the first click, it adds to the array, on the second click, it must delete it (index is written to the array)
function toggleFavoriteChanel(index) {
setFavorite(prevState => {
let returnArray = prevState;
if(prevState.includes(index)){
console.log(prevState)
console.log(index)
return returnArray.splice(prevState.indexOf(index), 1)
}else{
// here are 3 dots
return [..returnArray, index]
}
})
}
// <img src={star} alt="star".../>
{Array.isArray(props.visibleData) ? props.visibleData.map((chanel, index) => {
return (
<>
<div className="chanel__item" key={index}>
<img src={star} alt="star" onClick={() => props.toggleFavoriteChanel(index)} id={index} className={`star ${props.favorite.includes(index) ? 'active' : ''}`} />
<NavLink
onClick={() => props.updateData(index)}
end
style={{ textDecoration: 'none' }}
to='/ChanelPage'>
<img src={chanel.image} alt="" className="chanel__img" />
<div className="chanel__title"><div className="chanel__item-number">{index + 1}. </div>{chanel.name_ru}</div>
</NavLink>
</div>
</>
)
}) : null}
The issue is that you are setting favorite to the return value of splice, which is an array containing the deleted elements (from MDN docs on splice). What you want instead is to return returnArray after calling splice on it.
Just change this line in toggleFavoriteChanel:
return returnArray.splice(prevState.indexOf(index), 1)
to:
returnArray.splice(prevState.indexOf(index), 1);
return returnArray;
While the above should fix your issue, I would recommend approaching this problem in a different way if you are just trying to toggle a CSS class in response to clicking (assuming you don't need a list of the favorited cards at a higher level).
The approach is to define a component for the card and hold the isFavorite (clicked) state locally rather than in an array in an ancestral component.
Here's a rough example:
function Card(props) {
const [isFavorite, setIsFavorite] = React.useState(false);
return (
<div className="chanel__item">
<img
src={star}
alt="star"
onClick={() => setIsFavorite(prev => !prev)}
id={props.index}
className={`star ${isFavorite ? 'active' : ''}`}
/>
<NavLink
onClick={() => props.updateData(props.index)}
end
style={{ textDecoration: 'none' }}
to='/ChanelPage'>
<img src={chanel.image} alt="" className="chanel__img" />
<div className="chanel__title"><div className="chanel__item-number">{props.index + 1}. </div>{props.chanel.name_ru}</div>
</NavLink>
</div>
)
}

React - Framer Motion animates only the last item in map

so I have a state with an array, and I'm mapping it with framer motion animation. The problem is that I'm adding an item to the first position on the array, not at the end. So when a new item is added, instead of animating the new item added to the array, framer motion animates always the last one. Here is my code:
const [list, setList] = useState([])
const addToList = () => {
let newElement = (Math.random() + 1).toString(36).substring(7)
setList(prevState => ([newElement, ...prevState]))
}
return (
<div className="flex flex-col bg-white h-screen">
<button onClick={() => addToList()} className="bg-blue-500 p-2 rounded-lg text-white">Add to List</button>
<p className="mt-2">List:</p>
<div className="flex flex-col mt-4">
{
list.map((l, i) => {
return (
<motion.div initial={{ scale: 0 }} animate={{ scale: 1 }}>
<p key={i}>{l}</p>
</motion.div>
)
})
}
</div>
</div>
)
Always the same last item animates when a new item is added (instead of the new item that will be the first position at the array). How can I solve it?
Problem solved. I was using a key based on the array position of the element, so when a new element was added, React have to render everything again (I think). By using a unique key, React knows exactly what item was changed (added in this case). So the animation works fine.

Possible to render components in reverse manner in JSX?

In my Nextjs/React.js component I am rendering list of cards like this :
<div className="grid grid-cols-1 lg:grid-cols-12 gap-12">
<div className="lg:col-span-8 col-span-1">
{posts.map((post, index) => (
<PostCard post={post.node} key={post.title} />
))}
</div>
I was wondering if it was possible to render these PostCards in a reverse manner; starting from the last index rather than the initial index? This is for my blog application and whenever I post a new PostCard I want the latest Post to be rendered on Top of the list instead of the bottom of the list.
Just reverse the array first:
{posts.slice(0).reverse().map((post, index) => (
<PostCard
post={ post.node }
key={ post.title }
/>
))}
whenever I post a new PostCard I want the latest Post to be rendered on Top of the list instead of the bottom of the list.
If you're currently doing this by adding to the end of an array, you can add to the start of it instead. For example, if you're doing this:
setPosts(prev => [...prev, { title: 'My New Post' }]);
Do this instead:
setPosts(prev => [{ title : 'My New Post' }, ...prev]);
If you can't change the way the array gets created (say, because some components want it in one order, and some in another), then you can create a new array in the right order, and then map over that. You may want to memoize this if the array isn't going to change often:
const reorderedPosts = useMemo(() => {
return [...posts].reverse();
}, [posts]);
// ...
{reorderedPosts.map((post, index) => (
<PostCard post={post.node} key={post.title} />
))}
This can also easily be enhanced to let you change the order via a state, if you need to:
const [shouldReverse, setShouldReverse] = useState(false);
const reorderedPosts = useMemo(() => {
if (shouldReverse) {
return [...posts].reverse();
} else {
return posts;
}
}, [posts, shouldReverse])
// ...
{reorderedPosts.map((post, index) => (
<PostCard post={post.node} key={post.title} />
))}

Append slides dynamically in Swiper slider in React

I am creating a new application in React. I want to use the swiper slider for the banners.
The banners will be fetched from APIs. How do I append slides to the swiper once it reaches the last slide. I am using onReachEnd function to trigger the api call. Not sure how to append slides dynamically.
export const HeroSlider = () => {
const [pageNumber, setCurrentPageNumber ] = useState(0)
const { homeBannerData, isLoading, error } = useBannerData(pageNumber)
var dataHomeBanner = []
if (homeBannerData && homeBannerData.result) {
dataHomeBanner = dataHomeBanner.concat(homeBannerData.result.data)
}
function fetchBannerData() {
setCurrentPageNumber(pageNumber+1)
if (homeBannerData && homeBannerData.result) {
dataHomeBanner = dataHomeBanner.concat(homeBannerData.result.data)
}
}
return (
/* Hero Banner Start */
<section className="hero-slider">
<Swiper
slidesPerView={1}
watchSlidesVisibility = {true}
navigation
onReachEnd={() => fetchBannerData()}
autoHeight = {true}
>
{dataHomeBanner && dataHomeBanner.map((slide, index)=>{
return (
<SwiperSlide key={index}>
<img src={slide.image alt={slide.title} className="w-100"/>
<Container>
<div className="hero-slider-body d-flex align-items-end align-items-md-center">
<Row>
<Col md={12}>
<div className="hero-slider-caption">
<h1>{slide.title}</h1>
<p>{slide.description}</p>
</div>
</Col>
</Row>
</div>
</Container>
</SwiperSlide>
)
})}
</Swiper>
</section>
/* Hero Banner End */
)
}
useBannerData is returning the banner data.
Right now this code is adding the new slides and replacing the old ones once it fetches data with a new page number. I want it to append at the end of the last slides so that when a user reaches the last slide and clicks on next slide it should fetch data and append it at the end of last slide.
TIA!

How to avoid React Hook UseState to share the states?

I may have a bad title for this question, but here's my situation.
I use a chunk of json to render a list. The list item can be expanded and showed the sub list if it has children property. The json structure includes two arrays and each array contains more sub-arrays. I use tabs to switch arrays.
I use useState to manage the value isExpanded of each individual sub-array component. but it seems like the state isExpaned is shared for all tabs.
The state isExpanded remains same even if I switch to another tab. In other words, why the sub-list keep expanded when I switch to another tab?
In addition, why the expanded sub-list of each tab overlaps each other. They should keep 'close' when I switch to another tab because I set the initial state to false already. (const [isExpand, setIsExpand] = useState(false))
const ListItem = ({name, children}) => {
const [subList, setSubList] = useState(null)
const [isExpand, setIsExpand] = useState(false)
const handleItemClick = () => {
children && setIsExpand(!isExpand)
console.log(isExpand)
}
useEffect(() => {
isExpand && children && setSubList(children)
}, [isExpand, children])
return (
<div className='list-wrapper'>
<div className='list-item'>
{name}
{
children &&
<span
className='expand'
onClick={() => handleItemClick()}>
{isExpand ? '-' : '+'}
</span>
}
</div>
<div className='list-children'>
{
isExpand && subList && subList.map((item, index) =>
<ListItem key={index} name={item} />
)
}
</div>
</div>
)
}
Here's the codesanbox, anyone helps?
It seems like React is confused due to index being used as ListeItem key.
(React will try to "share" isExpanded state as they look the same according to the key you specified)
You could change the key from key={index}
<div className="contents">
{contents &&
contents.children &&
contents.children.map((item, index) => (
<ListItem
...... 👇 ....
key={index}
name={item.name}
children={item.children}
/>
))}
</div>
to use more distinct key, item.name
<div className="contents">
{contents &&
contents.children &&
contents.children.map(item => (
<ListItem
...... 👇 ....
key={item.name}
name={item.name}
children={item.children}
/>
))}
</div>
Check out the forked sandbox.
https://codesandbox.io/s/soanswer57212032-9ggzj

Resources