React Carousel Gallery
![thumbnail of article - React Carousel Gallery](/_next/image?url=https%3A%2F%2Fcdn.sanity.io%2Fimages%2Fyws6t3sb%2Fproduction%2Ff5fcad0883666b03f3916a5f914cbff81bcd7609-1920x1080.jpg&w=3840&q=75)
In the first step, we’ll create a React application. In the past, the go-to solution for this was a tool provided by the React developers called Create React App (CRA). However, there's now a better option available—Vite. Vite offers several advantages, such as faster development speed and smaller build sizes. For more details, feel free to visit the Vite website. Interestingly, Vite was created by Evan You, the developer behind Vue.js, one of React's main competitors.
Creating a React app is very simple. You just need to enter the command above into the terminal, name your project, and choose from several languages supported by Vite. I chose React with JavaScript.
npm create vite@latest
In this small project, I won’t focus too much on styling, as it's not the main goal. However, I’ll add some basic styling for better visibility using Tailwind via a CDN.
I have a JSON list of images, where each object includes a src
and an alt
attribute. Always remember to provide descriptive alternative text for images. If you prefer, you can use a simple array of image URLs in your app instead—the choice is yours.
{
"images": [
{
"src": "/dog-1.jpg",
"alt": "happy Border Collie"
},
{
"src": "/dog-2.jpg",
"alt": "two dogs playing"
},
{
"src": "/dog-3.jpg",
"alt": "smile of happy dog"
},
{
"src": "/dog-4.jpg",
"alt": "eyes and nose of brown dog"
},
{
"src": "/dog-5.jpg",
"alt": "walking dogs"
}
]
}
I created a useState
hook to store the index of the image I want to display. For now, the image index is hardcoded. It's important to understand that useState
is a React hook used to manage state in functional components. To toggle the visibility of images, I used a ternary operator that adds or removes the hidden
class. If the current state matches the image index, the image is visible; otherwise, the hidden
class is applied.
import { images } from './images.json'
import { useState } from 'react'
function App() {
const [slide, setSlide] = useState(0)
return (
<div className='w-1/2 mx-auto mt-32'>
{images.map((image, index) => (
<img src={image.src} alt={image.alt} key={index}
className={`${slide === index ? '' : 'hidden'}`} />
))}
</div>
)
}
export default App
Now it's time to add a bullet indicator. In our case, instead of bullets, we'll use numbers to represent each image for better clarity. This is quite simple; we'll follow a similar approach, but in this case, we only need the index, so we can ignore the image argument. To do this, we'll use _
as a placeholder, which is a common practice when only the second argument, the index, is needed. Don't forget to add 1 to the index when displaying the numbers, as array indices start from 0.
import { images } from './images.json'
import { useState } from 'react'
function App() {
const [slide, setSlide] = useState(0)
return (
<div className='w-1/2 mx-auto mt-32'>
{images.map((image, index) => (
<img src={image.src} alt={image.alt} key={index} className={`h-[400px] w-full object-cover
${slide === index ? '' : 'hidden'}`} />
))}
<div className='flex justify-center items-center mt-3 gap-4'>
{images.map((_, index) => (
<button key={index} onClick={()=>setSlide(index)}>{index + 1}</button>
))}
</div>
</div>
</div>
)
}
export default App
Now we come to an interesting point—why did I write the onClick
handler like this: onClick={() => setSlide(ndex)}
, wrapping the setSlide
function in an anonymous function? If we don't use an anonymous function, like in the first example, React throws an error about too many re-renders. This happens because the function is called immediately after the component renders, rather than when the button is clicked, leading to an infinite render loop. By placing the state setter inside an anonymous function, it only gets called when the button is clicked, and React re-renders the component only when necessary.
To add an "active" class to the indicator, we need to apply specific styles when the index matches the current state. If the index is the same as the state, we'll add the classes font-bold
and text-purple-500
to highlight the active indicator. In other cases, we won’t add any additional classes. This can be achieved using a classic example of a ternary operator.
<button key={index} onClick={() => setSlide(index)}
className={index === slide ? 'text-purple-500 font-bold' : ""}>
{index +1}
</button>
The final key feature of our carousel is the navigation buttons for showing the next or previous image. Let’s start with the function to show the previous image. We’ll create a prevSlide
function that uses the setSlide
function from useState
. It's crucial to use the previous state in this function to correctly determine which image to go back to. Additionally, when we reach index 0 and click the "previous" button, we need to wrap around to the last image in the list. This can be achieved by setting the index to images.length - 1
. For the next image functionality, we can use a similar approach but simply subtract 1 from the current index. The rest of the implementation involves using a classic ternary operator to handle these conditions.
const prevSlide = () => {
setSlide(prev => (slide === 0 ? images.length - 1 : prev - 1))
}
Finally, we have the nextSlide
function. This function is very similar to prevSlide
, but with a focus on what happens when we reach the last index and click the "next" button. In this case, we need to wrap around to the first image, which is index 0. This ensures that after the last image, the carousel will start again from the beginning. The implementation involves a similar approach to the previous function, with the addition of resetting to index 0 when the end of the list is reached.
const nextSlide = () => {
setSlide(prev => (slide === images.length - 1 ? 0 : prev + 1))
}
That’s full code of this simply carousel:
import { images } from './images.json'
import { useState } from 'react'
function App() {
const [slide, setSlide] = useState(2)
const prevSlide = () => {
setSlide(prev => (slide === 0 ? images.length - 1 : prev - 1))
}
const nextSlide = () => {
setSlide(prev => (slide === images.length - 1 ? 0 : prev + 1))
}
return (
<div className='w-1/2 mx-auto mt-32'>
{images.map((image, index) => (
<img
src={image.src}
alt={image.alt}
key={index}
className={`h-[400px] w-full object-cover ${slide === index ? '' : 'hidden'}`}
/>
))}
<div className='flex justify-center items-center mt-3 gap-4'>
<button onClick={prevSlide}>prev</button>
{images.map((_, index) => (
<button key={index} onClick={() => setSlide(index)} className={index === slide ? 'text-purple-500 font-bold' : ""}>
{index +1}
</button>
))}
<button onClick={nextSlide}>next</button>
</div>
</div>
)
}
export default App
Code: https://github.com/marekgacek45/reactMaster/tree/main/imageCarousel