Hey David!
Hope you are doing great.
Using Gatsby the slide I built works fine until I change to another page that uses the slider as well:
It seems, that although next etc still triggers, the slider looses the node list / slides.
I tried to force reinit with useEffect, but to no avail.
Interestingly the sliders starts to work again, when I change options, like for example draggable, but that could be of course because of the development hot reload.
Are you familiar with this issue?
What procedure would you suggest for this case for debugging?
Thank you and all the best!
Hi Leo (@Hahlh),
Thank you for your question. My initial hunch is that this isn鈥檛 an issue with Embla, because I鈥檝e used Embla with Gatsby before without any issues.
But I think I need to see some code to try to understand why this is happening.
Best,
David
what about refreshing embla script each time when route changes? for example remove sctipt tag and add it dynamicly by js after all html has been loaded from views
Hey @vladtabachuk !
Thank you for jumping in as well.
I didn't answer so far, because I tried to further boil it down to some possibilities.
Interestingly reInit on each next or prev call solves it, but causes a ton of unnecessary rerenders and causes the image to be refetched every time, if I am not mistaken. At the same time reInit in the useEffect hooks doesn't show any effects, which already hints at the source of the problem, I believe.
I have a deadline coming up the 3rd and will have to focus on other things until then, but as soon as that deadline passes, I will be able to properly debug again and hopefully also close this with a solution that will help others stumbling over the same issues.
@davidcetinkaya I hope it is okay for you to keep this open until then. If not, we can close it and I will comment and explain, when I have found a solution.
Thank you to you of course as well! Your answer times and dedication are impressive and I am glad, that I have found this repo!
Thank you for the kind words, i realy like this embla plugin, and currently using it on my website, and also glad to help to like-minded people, your project looks fancy i very like design, very creative. what about to link embla.css inside header of page, if it doesnt change dynamicly, if your app is SPA, made with vanilla js and custom routing system maybe <link rel="stylesheet" href="/static/js/libs/embla/embla.css"> will be better than import "./styles.css"; (react style of doing it) inside js that loads dynamicly by itself for my opinion. May be this way of usage will fix your issues. For example my website built with vanilla js and it have routing system with no frameworks so i have posibility to link styles and when page loads once it links and ech time i change routes it works fine, but who knows what could actualy be your problem. Because i see that styles loading after each time you change route and big not stylezid logo appears and then it scales down so, may be better will be declare theme change behaviour in some kind of central css file using css variables and js
Glad to help some how.
Best reggards, Vlad. www.tabachuk.vip
Hello Leo (@Hahlh),
No worries. Just let me know when you have time to investigate this further 馃憤. You shouldn't have to force a reInit with Embla. As mentioned before, I need more details in order to help you.
Kindly,
David
Hi Leo (@Hahlh),
Just wanted to ask if you have any updates on this?
Cheers,
David
I had to squeeze a project in.
It's awesome of you, that you ask, David! :)
Tomorrow I will have a thorough look at it and report back.
Thank you for your support!
Okay @davidcetinkaya .
I have stripped down and ripped out everything from the component (styles, unrelated code, ..) as I believe it to be likely that I am causing the problem in the setup / useEffects through simple mistakes, so that a render is not triggered on page change.
That means, that this code is meant for you to be easily graspable at a glance, almost like pseudo code, but won't really work for copy & pasting it somewhere to run it.
If you don't spot the error on a first glance or simply prefer something to run, please tell me and I will create a clean version within codesandbox.
Here it is:
import React, { useState, useEffect, useCallback } from "react"
import { useEmblaCarousel } from "embla-carousel/react"
// import { PrevButton, NextButton } from "./EmblaCarouselButtons";
const viewportCss = {
overflow: "hidden",
}
const containerCss = {
display: "flex",
}
const slideCss = {
position: "relative",
minWidth: "100%",
}
export const ProductPresentationPlug = ({
data,
fragmentData,
}) =>
{
// Embla Setup
const [viewportRef, embla] = useEmblaCarousel({
loop: true,
draggable: true,
})
const [prevBtnEnabled, setPrevBtnEnabled] = useState(false)
const [nextBtnEnabled, setNextBtnEnabled] = useState(false)
const [currentSnap, setCurrentSnap] = useState(0)
const [numberOfSnaps, setNumberOfSnaps] = useState(0)
const scrollPrev = useCallback(() => {
setCurrentSnap(() => embla.selectedScrollSnap())
// embla.reInit()
return embla && embla.scrollPrev()
}, [embla])
const scrollNext = useCallback(() => {
setCurrentSnap(() => embla.selectedScrollSnap())
// embla.reInit()
return embla && embla.scrollNext()
}, [embla])
const onSelect = useCallback(() => {
if (!embla) return
setPrevBtnEnabled(embla.canScrollPrev())
setNextBtnEnabled(embla.canScrollNext())
}, [embla])
useEffect(() => {
if (!embla) return
setCurrentSnap(() => embla.selectedScrollSnap())
setPrevBtnEnabled(() => embla.canScrollPrev())
setNextBtnEnabled(() => embla.canScrollNext())
}, [])
useEffect(() => {
if (!embla) return
setNumberOfSnaps(() => embla.scrollSnapList().length)
}, [embla])
useEffect(() => {
if (!embla) return
embla.on("select", onSelect)
onSelect()
}, [embla, onSelect])
// Embla Setup End
const imagesJSX = images.map(item => {
return (
<div
key={item._key}
style={slideCss}
>
<SanityImage />
</div>
)
})
return (
<>
<div>
<div>
{/* Embla Carousel */}
<div
className={`overflow-hidden`}
style={viewportCss}
ref={viewportRef}
>
<div>{imagesJSX}</div>
</div>
<div className="">
<button
// disabled={!prevBtnEnabled}
onClick={scrollPrev}
>
<svg />
</button>
<div>
{currentSnap + 1} <span>/</span>{" "}
{numberOfSnaps}
</div>
<button
// disabled={!nextBtnEnabled}
onClick={scrollNext}
>
<svg />
</button>
</div>
</div>
</div>
</>
)
}
Hi Leo (@Hahlh),
I took the liberty to add syntax highlighting to your code, I hope that's ok 馃檪. At this point I don't have a clear picture of the issue you're experiencing. You're mentioning that Embla is loosing slides. Does that mean that your carousel is global (meaning that it doesn't unmount when you navigate to another route)? Is it placed inside the Gatsby Layout component?
Looking at the code, you can make some changes to make it shorter:
const scrollPrev = useCallback(() => {
setCurrentSnap(() => embla.selectedScrollSnap())
// embla.reInit()
return embla && embla.scrollPrev()
}, [embla])
const scrollNext = useCallback(() => {
setCurrentSnap(() => embla.selectedScrollSnap())
// embla.reInit()
return embla && embla.scrollNext()
}, [embla])
const onSelect = useCallback(() => {
if (!embla) return
setPrevBtnEnabled(embla.canScrollPrev())
setNextBtnEnabled(embla.canScrollNext())
}, [embla])
useEffect(() => {
if (!embla) return
setCurrentSnap(() => embla.selectedScrollSnap())
setPrevBtnEnabled(() => embla.canScrollPrev())
setNextBtnEnabled(() => embla.canScrollNext())
}, [])
useEffect(() => {
if (!embla) return
setNumberOfSnaps(() => embla.scrollSnapList().length)
}, [embla])
useEffect(() => {
if (!embla) return
embla.on("select", onSelect)
onSelect()
}, [embla, onSelect])
const scrollPrev = useCallback(() => embla && embla.scrollPrev(), [embla])
const scrollNext = useCallback(() => embla && embla.scrollNext(), [embla])
const onSelect = useCallback(() => {
if (!embla) return
setPrevBtnEnabled(embla.canScrollPrev())
setNextBtnEnabled(embla.canScrollNext())
setCurrentSnap(embla.selectedScrollSnap())
}, [embla])
useEffect(() => {
if (!embla) return
setNumberOfSnaps(embla.scrollSnapList().length)
embla.on("select", onSelect)
onSelect()
}, [embla, onSelect])
There's no need to setCurrentSnap manually in your scrollPrev and scrollNext functions or anywhere else. Just add it to onSelect which is automatically called when Embla fires the select event in this piece of code:
embla.on("select", onSelect)
Another thing I noticed is that your HTML and CSS setup for the carousel doesn't seem to be correct:
<div
className={`overflow-hidden`}
style={viewportCss}
ref={viewportRef}
>
<div>{imagesJSX}</div> {/* This div is missing the required container styles? */}
</div>
According to the docs, you should have the following HTML structure:
import React from 'react'
import { useEmblaCarousel } from 'embla-carousel/react'
const EmblaCarousel = () => {
const [emblaRef] = useEmblaCarousel()
return (
<div className="embla" ref={emblaRef}>
<div className="embla__container">
<div className="embla__slide">Slide 1</div>
<div className="embla__slide">Slide 2</div>
<div className="embla__slide">Slide 3</div>
</div>
</div>
)
}
export default EmblaCarousel
...and the minimum required CSS for that would be:
.embla {
overflow: hidden;
}
.embla__container {
display: flex;
}
.embla__slide {
position: relative;
flex: 0 0 100%; /* Change 100% to any slide size you want */
}
I also had troubles figuring out where the images variable comes from?
const imagesJSX = images.map(item => {
return (
<div
key={item._key}
style={slideCss}
>
<SanityImage />
</div>
)
})
So what I would like to mention is that if you're passing the images variable as a prop to the carousel, Embla won't pick up any changes automatically if that prop changes. So you need to do something along these lines to tell Embla that the slide count changed:
useEffect(() => {
if (embla && embla.slideNodes().length !== images.length) {
embla.reInit() // Make Embla pick up changes in slide count
}
}, [embla, images])
Let me know how it goes.
Best,
David
Thank you for thorough comment, David!
Adding syntax highlighting was definitely okay! :slightly_smiling_face:
Reminds me to use it more consistently in comments.
I should have been a little clearer with the 'loosing slides' part.
You should be able to see the empty node list at minute 1 in the video of the first post.
It could also be, that I am just misinterpreting, what kind of content it should contain in a functioning state and what information of the object I can actually rely on for debugging.
The component is a child of component that gets dynamically added to all product pages, so it should unmount on route change.
productPage (template) > list of added modules for the specific page comes down from the CMS > among them productPresentationPlug which contains embla and is linked to the associated data.
Thank you as well for the refactoring. Your code is much more concise.
Your points:
I am using the currentSnap state for outputting the current active slide number:

This should also be possible through embla directly and I am not quite sure anymore, why I decided to not go that route.
Possibly it was, because the current issue prevented this from functioning correctly and I had to develop a temporary functioning approach for a presentation (really really unrealistic deadlines -> 16+ hours days).
I use Tailwind and I stripped out almost everything that would make it harder for you to read the logic. A little bit a gambit, as it presumed, that it would be easier for you to simply look at the code and easily spot the fault, than run and debug it. So basically assuming a rookie error by myself.
The needed classes were there and shouldn't be part of the problem, as displaying also seems to work fine before a route change.
The images comes as a array down from a CMS (sanity). The available images should not change within a specific page, but I assume, that React not unmounting the component as it is reused on the next page could trigger that issue? That's easy to check though.
I will also check whether your workaround works, simplify the code and post the complete component without anything stripped out, if the problem still persists (I hope you are fine with tons of CSS-in-JS :sweat_smile:). If it's solved, I will report back with the solution for the benefit of future generations :+1:
On a side note:
Did you update the docs?
I remember them to be good, but they are on a whole new level now? Or is that just me and little sleep looking at them last time?
Thank you again, David!
Hi Leo (@Hahlh),
Thank you for your response and explaining further. That sounds like a plan, try the following I mentioned when you have some time to dig into it:
useEffect(() => {
if (embla && embla.slideNodes().length !== images.length) {
embla.reInit() // Make Embla pick up changes in slide count
}
}, [embla, images])
...and let me know if it helps.
Did you update the docs?
I remember them to be good, but they are on a whole new level now? Or is that just me and little sleep looking at them last time?
I did. The previous documentation website was something I hacked together in a day or so. I didn't have much time so I used Create React App and hard coded everything including the content, just to give Embla users something to start with.
I created the new documentation website from scratch using GatsbyJs. Thanks to a some donations from some of my users, I was able to spend some real quality time with it. So I decided to improve both the visual looks and build a conventional documentation website. All content is written in Markdown which makes it really easy to edit and create new content.
I'm hoping to get more time soon to add more content under the Guides section, that shows users how to achieve specific goals. I hope you like it.
Cheers!
David
Good morning David!
I have now implemented your refactoring. The code is now much cleaner and some additional problems vanished :)
I also implemented the useEffect you proposed, but unfortunately, this doesn't solve the issue of the slider not working on route change.
First I thought that that might be, because if (embla && embla.slideNodes().length !== images.length) would only trigger, if the slide counts would not be identical. It wouldn't trigger, if they were different slides but the same amount.
I tried to force a reinit using using images.length + 5 which would mean that slide counts should always be different, but not no avail.
Or should there be an intermittent state, where the slide count would be 0 and would therefore trigger the reinit?
My current guess is, that this relates to the same component for the slides being used on different pages.
The funny thing is, that embla is showing the correct first image on page change, but doesn't work with dragging or the arrows, although the number below changes, meaning that embla is working in the background without the slides updating?
Please excuse, that the feedback loop has been so slow so far. I am now able to devote much more time to this again and will be able to decrease the amount of time between answers significantly.
I can force it to update the slides, if I force an embla reinit on every render:
useEffect(() => {
if (embla) {
embla.reInit()
}
})
I can't force it to work, if I force an reinit on every mount:
useEffect(() => {
if (embla) {
embla.reInit()
}
}, [])
That should mean, that the component is indeed not unmounting, which could cause the problem.
I will investigate further.
Is there any general information I could provide, for example the embla object when the slides are "blocked", that could shine some light on what is happening?
Hi Leo (@Hahlh),
Thank you for the additional information. It鈥檚 very hard to do any debugging without your exact setup. All I can say at this point is that you鈥檒l have to manually initialize Embla when any prop or other state affecting the carousel changes, using a useEffect. Because if your slides and everything Embla needs are in place by the time you initialize or re-initialize Embla, everything will work as expected.
The last thing I can suggest without your setup available is to do this:
useEffect(() => {
if (embla) {
embla.reInit()
}
}, [embla, slides])
This code will run as soon as anything related to the slides changes.
If you don鈥檛 have any luck with that, my suggestion is to create a CodeSandbox with the gatsby-starter-template they offer and reproduce the behavior there.
I hope this makes sense.
Best,
David
I am already in the process of creating a codesandbox for reproduction and will also check whether your suggestion resolves the issue :+1:
I am already in the process of creating a codesandbox for reproduction and will also check whether your suggestion resolves the issue 馃憤
Awesome. Let鈥檚 hope that this will shed some light on why this is happening.
The CodeSandBox for reproduction.
Unfortunately I have so far failed to reproduce the issue in this environment.
I first speculated, that the source of the data could be the issue. Therefore I used the same JSON data source as in the CodeSandBox in the original project, but this still failed to resolve the issue.
This means that this issue is likely unrelated to the component and embla itself and that it is likely caused by a parent.
I will probably stumble over that issue as I clean up and optimize the project.
The temporary solution is to get the current location:
import { globalHistory as history } from "@reach/router"
```javascript
const { location } = history
and force a reinit on location change:
```javascript
useEffect(() => {
if (embla) {
embla.reInit()
}
}, [embla, location.pathname])
Hopefully that helps somebody in the future as they encounter a similar issue.
I will close this issue for now and thank you again for your incredible help, David! :pray:
Edit: It seems that the combination of JSON as data source and forcing a reinit on route change solved the issue. Going back to the CMS as a data source reintroduces the problem. I will keep this closed nevertheless until I have figured out what is going on.
Thanks for the information Leo @Hahlh. I hope that you'll find what's causing this and if you do, let me know in this issue.
On a side note:
This is just a picky detail but you can import the useLocation hook from reach router like so:
import { useLocation } from '@reach/router'
const MyComponent = () => {
const { pathname } = useLocation()
useEffect(() => {
if (embla) embla.reInit()
}, [embla, pathname])
}
Cheers
David
Please keep those picky details coming :)
Learning a lot from your code!
I will post, if I find a solution :+1:
Most helpful comment
Hi again Leo (@Hahlh),
Just wanted to mention that I added a new guide to the docs that demonstrates how to pick up changes in slides. It's pretty much the same approach I mentioned in my earlier comment.
Best,
David