In my last article, we discussed the useState()
hook and did a simple example. You could wonder "How do I use this in a real-life project"? I've got you, newbie. We'll be creating an image slider that renders a new image when we click on some buttons. Have a sneak peek below:
First, we install React since we need useState()
hook from the React library. I use Vite to bundle React since it's recommended by the React team. Open a terminal and write the command below then follow the instructions shown in your command line thereafter:
npm create vite@latest image-slider-app -- --template react
Delete the index.css file. Clear the App.jsx and App.css file as we would create new components and write our styles.
First, let's create our component. Since it's a simple app, we would be creating just one component that contains the images and arrows. Write the below code in the App.jsx file
import "./App.css";
import back from "./assets/back.svg";
import next from "./assets/next.svg";
function App() {
return (
<main>
<section>
<div>
<img
src={back}
alt=""
width={50}
height={50}
className="icon-left"
/>
<img
src={next}
alt=""
width={50}
height={50}
className="icon-right"
/>
<img src="https://i.pinimg.com/originals/10/11/bc/1011bcc380b230ecb422589c990e38ec.jpg" alt="" className="main-image" />
</div>
</section>
</main>
);
}
export default App;
The code above consists of our sliding image and the arrows we use to change them. You can find these arrows in the code repository I posted above. In the first line, we can find the CSS import but we don't have our styles yet. Well, I'm so generous and I decided to share the CSS styles below so our project looks good.
*,
::after,
::before {
margin: 0;
box-sizing: border-box;
padding: 0;
}
main section {
width: 100%;
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
}
main section div {
width: 100%;
max-width: 700px;
position: relative;
margin: 2rem;
}
main section div img.main-image {
width: 100%;
height: 500px;
object-fit: cover;
}
.icon-left {
position: absolute;
top: 50%;
left: -3%;
}
.icon-right {
position: absolute;
top: 50%;
right: -3%;
}
We have the following result
Hold up! We're clicking the buttons but the images are not changing. Why?
💡 Remember, anytime we need to change a state that reflects in the user interface, we use the
useState()
hook. This guarantees that our state and user interface are in sync.
Do you remember what we do first? Yeah! We import the useState()
hook in the top level(first line in the App.jsx file)
import { useState } from 'react'
After importing it, we need to initialize it in our app. We do this by saying:
const [activeImage, setActiveImage] = useState(0)
Let's think about the logic we'll be using in this simple app. Firstly, we need 5 images. When we click the next button, the image moves to the next one and when we click previous it moves back. Seems like something our dear useState()
can help us with since we also want to see the changes in the interface.
Now where do we get 5 images? Well, I got you. I made an array of 5 images src that we're going to make us of in this practical
const imagesArray = [
"https://i.pinimg.com/originals/e6/72/c9/e672c9fe478daac0019c9235e3a9794c.jpg",
"https://i.pinimg.com/originals/10/11/bc/1011bcc380b230ecb422589c990e38ec.jpg",
"https://littlelosttravel.com/wp-content/uploads/2020/11/Finland.jpg",
"https://littlelosttravel.com/wp-content/uploads/2020/11/Hawaii-jelle-de-gie-u.jpg",
" https://littlelosttravel.com/wp-content/uploads/2020/11/Morocco-sergey-pesterev-u.jpg",
];
We don't add this inside the App()
, we add it just after importing useState()
from React at the top level. Why? Surely our state would always change, meaning the component would be re-rendered. When this happens, everything inside the function App() {}
would be created again. It has little or no effect in this case since imagesArray
is not a large chunk of data, but re-rendering, especially large data affects performance. There's a solution to this but it's beyond the scope of this article.
Now we have our images src, we also need to add event listeners to our arrow so that whenever we click on them, they change accordingly.
html
<img
src={back}
alt=""
width={50}
height={50}
className="icon-left"
onClick={goToPreviousImage}
/>
<img
src={next}
alt=""
width={50}
height={50}
className="icon-right"
onClick={goToNextImage}
/>
We have our event listeners and the respective functions they should call when they're clicked on. Now let's make those functions work.
For the function that goes to the next image, we have this:
const goToNextImage = () => {
setActiveImage(activeImage + 1);
console.log(activeImage)
};
This increments the activeImage
by one anytime the next arrow is clicked.
const goToPreviousImage = () => {
setActiveImage(activeImage - 1);
console.log(activeImage)
};
This decrements the activeImage
by one anytime the previous arrow is clicked.
Before testing out our features, we need to make sure the image index shown corresponds with the activeImage
number. Back to our App.jsx file, just before the closing div
, we change the src of the image to imagesArray[activeImage]
. Why? The activeImage
state is a number which we use to represent the index of the image since we're using an array. For example, on entering the App for the first time, the activeImage
state is 0(as we initialized it) so following our logic we have imagesArray[0]
which renders the first image in the array. So now for our main image we have
<img src={imagesArray[activeImage]} alt="" />
We also console.log()
the activeImage
when the arrow is clicked so we can see how it really works. Now we have this:
Well... it works and broke halfway. From what we can see the activeImage
reads more than the available index in the array. For example, we have just 5 items in our array, so the highest index we can get is 4
. But the active index reads to infinity as far as we click. For the previous array, it reads less than 0(since we said it should be decremented) and we know from our knowledge of javascript, an array index can't be negative. That's why we have codes breaking.
What can we do to solve this problem? Well, we can need to add an if
statement. When the activeImage
count is more than the last index in the array, we want to set it back to 0 and when the activeImage
count is less than 0, we want to set it back to the index of the last item in the array. See the code snippet below:
const goToNextImage = () => {
if (activeImage === imagesArray.length - 1) {
setActiveImage(0);
return;
}
setActiveImage(activeImage + 1);
console.log(activeImage);
};
const goToPreviousImage = () => {
if (activeImage === 0) {
setActiveImage(imagesArray.length - 1);
return;
}
setActiveImage(activeImage - 1);
console.log(activeImage);
};
With this, our issue should be fixed. Let's see...
Yeah!! We did it, and now it works. When the activeImage
is more than the index available in the array, it automatically sets the activeImage
to 0 and when the `activeImage is less than 0, it sets it back to the index of the last item in the array.
Well, we did it! Our image slider works the way it should. If you were quite confused about how useState()
actually works, here is a previous article:
Follow me so I can use written words to simplify the mysteries behind React and Javascript! Don't forget to Like, comment and share!
Top comments (2)
Another Article Well Written.
Thank you for adding the a link to the code repo too.
Welldone 👏👏👏👏
Wow…. This is gold Meemah👏
This is really helpful.
Thank you