I am trying to implement a slide transition between books. I've tried Motion, but I am getting weird animations. All I am looking for is to slide each book across the x axis when ever they are being sorted or shuffled.
My Tech stack:
Here is my code:
// Page.js (parent)
"use client";
import Book from "./Book";
import { useEffect, useState } from "react";
export default function Home() {
const [books, setBooks] = useState([]);
const [sortOrder, setSortOrder] = useState({ title: "asc", dateFinished: "asc", year: "asc" });
useEffect(() => {
async function fetchBooks() {
try {
const response = await fetch("/books.json");
if (!response.ok) {
throw new Error(`Failed to fetch: ${response.statusText}`);
}
const data = await response.json();
} catch (error) {
console.error("Error fetching books:", error);
}
}
fetchBooks();
}, []);
const sortBooks = (criteria) => {
const order = sortOrder[criteria] === "asc" ? "desc" : "asc";
const sortedBooks = [...books].sort((a, b) => {
switch (criteria) {
case "title":
return order === "asc" ? a.title.localeCompare(b.title) : b.title.localeCompare(a.title);
case "year":
return order === "asc" ? a.year - b.year : b.year - a.year;
// Add other sorting methods here
default:
return 0;
}
});
setBooks(sortedBooks);
setSortOrder({ ...sortOrder, [criteria]: order });
};
const handleSelectBook = (book) => {
if (selectedBook === book) {
setSelectedBook(null);
return;
}
setSelectedBook(book);
};
const handleShuffleBooks = () => {
setBooks(shuffleArray([...books]));
};
return (
<div className="overflow-y-hidden max-h-[100vh] max-w-[100vw] overflow-hidden">
<h1 className="text-center">My Bookshelf</h1>
{/* buttons */}
<div className="flex gap-2">
<button
onClick={() => sortBooks("title")}
>
Sort Alphabetically ({sortOrder.title === "asc" ? "A-Z" : "Z-A"})
</button>
<button
onClick={() => sortBooks("year")}
>
Sort by Year ({sortOrder.year === "asc" ? "Oldest First" : "Newest First"})
</button>
<button
onClick={handleShuffleBooks}
>
🔀
</button>
</div>
{/* Bookshelf */}
<ul className="max-h-[100vh] flex border-b-[40px] border-orange-900 mt-10 items-baseline mx-8 px-8 overflow-x-scroll w-full">
{books.map((book, index) => (
<Book
key={index}
data={book}
/>
))}
</ul>
</div>
);
}
// Book.js (child)
"use client";
import React from "react";
import { useState } from "react";
import Image from "next/image";
export default function Book({ data }) {
const [imageSize, setImageSize] = useState({ width: 0, height: 0 });
const handleImageLoad = ({ target }) => {
setImageSize({ width: target.naturalWidth / 4, height: target.naturalHeight / 4 });
};
return (
<motion.li className="relative flex gap-2 items-end">
<Image
src={`/images/${route}`}
width={imageSize.width}
height={imageSize.height}
unoptimized={true}
/>
<motion./li>
);
}
To achieve a smooth slide transition along the x-axis when books are sorted or shuffled, you need to ensure that the motion components are properly configured.
Here's a step-by-step guide to help you achieve the desired slide transition:
Page.js (parent component):
"use client";
import Book from "./Book";
import { useEffect, useState } from "react";
import { motion, AnimatePresence } from "framer-motion";
export default function Home() {
const [books, setBooks] = useState([]);
const [sortOrder, setSortOrder] = useState({ title: "asc", dateFinished: "asc", year: "asc" });
useEffect(() => {
async function fetchBooks() {
try {
const response = await fetch("/books.json");
if (!response.ok) {
throw new Error(`Failed to fetch: ${response.statusText}`);
}
const data = await response.json();
setBooks(data); // Set the fetched books to the state
} catch (error) {
console.error("Error fetching books:", error);
}
}
fetchBooks();
}, []);
const sortBooks = (criteria) => {
const order = sortOrder[criteria] === "asc" ? "desc" : "asc";
const sortedBooks = [...books].sort((a, b) => {
switch (criteria) {
case "title":
return order === "asc" ? a.title.localeCompare(b.title) : b.title.localeCompare(a.title);
case "year":
return order === "asc" ? a.year - b.year : b.year - a.year;
// Add other sorting methods here
default:
return 0;
}
});
setBooks(sortedBooks);
setSortOrder({ ...sortOrder, [criteria]: order });
};
const handleShuffleBooks = () => {
const shuffledBooks = [...books].sort(() => Math.random() - 0.5);
setBooks(shuffledBooks);
};
return (
<div className="overflow-y-hidden max-h-[100vh] max-w-[100vw] overflow-hidden">
<h1 className="text-center">My Bookshelf</h1>
{/* buttons */}
<div className="flex gap-2">
<button onClick={() => sortBooks("title")}>
Sort Alphabetically ({sortOrder.title === "asc" ? "A-Z" : "Z-A"})
</button>
<button onClick={() => sortBooks("year")}>
Sort by Year ({sortOrder.year === "asc" ? "Oldest First" : "Newest First"})
</button>
<button onClick={handleShuffleBooks}>
🔀
</button>
</div>
{/* Bookshelf */}
<ul className="max-h-[100vh] flex border-b-[40px] border-orange-900 mt-10 items-baseline mx-8 px-8 overflow-x-scroll w-full">
<AnimatePresence>
{books.map((book, index) => (
<Book key={book.id} data={book} index={index} />
))}
</AnimatePresence>
</ul>
</div>
);
}
Book.js (child component):
"use client";
import React, { useState } from "react";
import Image from "next/image";
import { motion } from "framer-motion";
export default function Book({ data, index }) {
const [imageSize, setImageSize] = useState({ width: 0, height: 0 });
const handleImageLoad = ({ target }) => {
setImageSize({ width: target.naturalWidth / 4, height: target.naturalHeight / 4 });
};
return (
<motion.li
className="relative flex gap-2 items-end"
initial={{ x: -100, opacity: 0 }}
animate={{ x: 0, opacity: 1 }}
exit={{ x: 100, opacity: 0 }}
transition={{ duration: 0.5 }}
layout
>
<Image
src={`/images/${data.route}`}
width={imageSize.width}
height={imageSize.height}
unoptimized={true}
onLoad={handleImageLoad}
/>
</motion.li>
);
}