Skip to content
Advertisement

How to pass correct state value into callback function inside useEffect hook?

I’m trying to change a state of a variable that holds photo ID when a user presses arrow keys or clicks on an image, then I render my image depending on that photo ID.

CODE:

const Lightbox = ({ filteredPhotos }) => {
const [currentPhotoId, setCurrentPhotoId] = useState(null);
const currentPhoto = filteredPhotos.filter((photo) => photo.strapiId === currentPhotoId)[0];
let lightbox = "";

const getLastPhotoId = (filteredPhotos) => {
    const ids = filteredPhotos.map((item) => item.strapiId).sort((a, b) => a - b);
    const result = ids.slice(-1)[0];
    return result;
};

//Select image on click
const selectImage = useCallback(
    (e) => {
        const divId = parseInt(e.target.parentElement.parentElement.className.split(" ")[0]);
        const imgSelected = e.target.parentElement.parentElement.className.includes("gatsby-image-wrapper");
        imgSelected && divId !== NaN ? setCurrentPhotoId(divId) : setCurrentPhotoId(null);
    },
    [setCurrentPhotoId]
);

//Change image on keypress
const changeImage = useCallback(
    (e, currentPhotoId) => {
        if (document.location.pathname !== "/portfolio" || currentPhotoId === null) return;
        const key = e.keyCode;
        console.log("changeImage start: ", currentPhotoId);
        if (key === 27) {
            setCurrentPhotoId(null);
        } else if (key === 39) {
            setCurrentPhotoId(currentPhotoId + 1);
        } else if (key === 37) {
            if (currentPhotoId - 1 <= 0) {
                setCurrentPhotoId(getLastPhotoId(filteredPhotos))
            } else {
                setCurrentPhotoId(currentPhotoId - 1)
            }
        }
    },
    [setCurrentPhotoId]
);

useEffect(() => {
    const gallery = document.getElementById("portfolio-gallery");
    gallery.addEventListener("click", (e) => selectImage(e));
    document.addEventListener("keydown", (e) => changeImage(e, currentPhotoId));
}, [selectImage, changeImage]);

useEffect(() => {
    console.log(currentPhotoId);
}, [currentPhotoId]);

return currentPhotoId === null ? (
    <div id="lightbox" className="lightbox">
        <h4>Nothing to display</h4>
    </div>
) : (
    <div id="lightbox" className="lightbox lightbox-active">
        <img src={currentPhoto.photo.childImageSharp.fluid.src} alt={currentPhoto.categories[0].name} />
    </div>
);
};

export default Lightbox;

Setting up a state by clicking/un-clicking on an image works without a problem, the state is set to a correct number.

But my function that handles keydown events is returned because my state currentPhotoId is null, and I don’t get it why when I’ve set my state by selecting an image.

If I add currentPhotoId in useEffect dependency array

useEffect(() => {
    const gallery = document.getElementById("portfolio-gallery");
    gallery.addEventListener("click", (e) => selectImage(e));
    document.addEventListener("keydown", (e) => changeImage(e, currentPhotoId));
}, [selectImage, changeImage, currentPhotoId]); //here

it breaks my selectImage (click) function. And also the more times user presses right arrow key, the more times it updates the state, resulting into so many updates it crashes down site eventually.

What am I doing wrong? Why my state isn’t updating correctly?

Advertisement

Answer

FIX:

useEffect(() => {
    document.addEventListener("keydown", changeImage); //invoking not calling
    return () => {
        document.removeEventListener("keydown", changeImage);
    };
}, [changeImage, currentPhotoId]);


const changeImage = useCallback(
    (e) => {
        if (document.location.pathname !== "/portfolio" || currentPhotoId === null) return;
        const key = e.keyCode;
        if (key === 27) {
            setCurrentPhotoId(null);
        } else if (key === 39) {
            setCurrentPhotoId(currentPhotoId + 1);
        } else if (key === 37) {
            if (currentPhotoId - 1 <= 0) {
                setCurrentPhotoId(getLastPhotoId(filteredPhotos));
            } else {
                setCurrentPhotoId(currentPhotoId - 1);
            }
        }
    },
    [setCurrentPhotoId, currentPhotoId] //added currentPhotoId as dependency
);

So in useEffect I was making a mistake with calling my function, instead of invoking it and this was just adding event listeners to infinite.

And in my callback function instead of passing the state as an argument, I’ve added id as a dependency.

Also, I’ve separated selectImage and changeImage into two useEffects, for selectImage useEffect I don’t have currentPhotoId as a dependency.

If somebody wants to elaborate more on the details, feel free to do so.

Advertisement