In the previous lesson, we created a Product
component to display the details of a product along with its image. In this lesson, we will extend the Product
component to display a set of images as a carousel instead of a single image:
To achieve this, we will create a new component, Carousel
, and then use it in the Product
component. The Carousel
component should display an image along with two arrow buttons. Additionally, it should update the displayed image when the user interacts with the arrow buttons.
Rather than constructing the entire component in one go, we will adopt an incremental approach by building the component as small and implementable layers.
Let’s get started by creating a Carousel.jsx
file for this component:
As the first step, we will display an image along with two arrow buttons. We will use the Button component from neeto-ui along with Left and Right icons from neeto-icons for previous and next buttons. For the Carousel
component to be reusable, we will add an imageUrls
prop to receive a set of URLs as an array of strings. Initially, we can display the first image from the imageUrls
we have passed.
By keeping the principles of web accessibility in mind, we will add the alt
attribute to the img
tag to display an alternate text if the image doesn't load. This text will also be used by the screen readers to read the content loud for the visually impaired readers. We will add a title
prop to the Carousel
component for the same:
We can use this component inside our Product
component to display a Carousel. For that, we need a set of images. We can use some dummy images from the internet for the same:
Let us create a constant array of these URLs. At Bigbinary, we follow the convention of naming constants in UPPER_SNAKE_CASE
and grouping them within a file constants.js
. Following this convention, let's create a constants.js
file within src/components
and place the above URLs into an array:
Now, we can replace the img
tag in the Product
component with the Carousel
component:
Our next goal is to add interactivity to the Carousel
component by enabling users to navigate between product images by clicking the arrow buttons.
Let's break down this requirement further:
-
Currently, we are passing the URL at the 0
th index of the imageUrls
array as the src
prop. Instead of hardcoding 0
, we need to make the array index dynamic. Whenever this index changes, we need to re-render the image with the new src URL. To achieve this, we will use a React feature called State.
-
Next, we need to increment or decrement the index when the user clicks on the next or previous buttons. For that, we need to listen to button clicks. We will learn the concept of Event handlers in React to achieve this.
State in React
To store dynamic values associated with a component, we can use the concept of state from React. React state stores a value, keeps track of its changes, and updates the user interface accordingly.
Most of React's powerful features, including states, are made accessible through special functions known as hooks. One such essential hook is useState
, which enables us to create states. It accepts an initial value and returns an array containing the current value of the state variable and a function to update this state variable. Here is the syntax for using the useState
function:
Here we have destructured the array returned by the useState
function to the variables state
and setState
. The first destructured variable is the name of the item we're tracking. The second variable prefixes that name with set
, signifying that it's a function that can be invoked to update the tracked item. This function is sometimes referred to as a setter function since it sets the new value for the state variable.
We can name these variables whatever we want. However, it is a convention followed across the React community to name the setter function as set
followed by the state variable name.
For example, we can name the state variable to store the current image index in our Carousel
component as currentIndex
and the setter function as setCurrentIndex
. Since we need to display the first image from the imageUrls
array on page load, we will set the initial value of currentIndex
as 0
:
Let's add the state variable currentIndex
to our Carousel
component using the useState
hook:
There are a couple of rules that we need to follow while using hooks
:
- Hooks should only be called within React components. It is not valid to call hooks outside components.
- Hooks should be called at the top level of the React component, before any early returns. This means that hooks cannot be called inside loops, conditions, or nested functions.
Event handling
You might be familiar with handling events in JavaScript. In JavaScript, browser events are handled by attaching event listeners to DOM elements:
HTML offers another way to handle events using event attributes:
React follows a pattern similar to HTML for event handling, but there are two key distinctions:
- React event handler props are named using camelCase instead of lowercase like,
onClick
, onChange
, onKeyDown
, etc.
- With JSX, we pass the function reference as the event handler instead of a string.
Here is an example of handling an event in React:
For onClick
, onChange
, setter functions, and similar event handlers, it is a common convention to name the functions with a handle
prefix, such as handleClick
, handleChange
, etc.
Let's jump into adding event handlers for the buttons in the Carousel
component.
We need to increment the currentIndex
when the user clicks the right arrow button and decrement it when clicking the left arrow. To ensure that the value of currentIndex
stays within the array index range, we should take the modulus with the array length. We can utilize the setCurrentIndex
function to update the currentIndex:
Go ahead and open your application to confirm the changes. You should now be able to navigate between the product images:
Let's reinforce what we have learned so far by adding an indicator to navigate between images:
Following are the steps to accomplish the above requirement:
- Display a set of dots indicating the number of images in the
imageUrls
.
- Clicking each dot should update the
currentIndex
. For example, clicking the 1st dot should update the currentIndex
to 0, clicking the 2nd dot should update the currentIndex
to 1 and so on.
- Based on the
currentIndex
, change the color of the dot corresponding to the current image to black.
Before jumping straight into the solution, try to solve this yourself, with the knowledge about state and events handling.
Solution
Let’s see how we can add the dot indicator to our Carousel
component.
- First, we will map over the
imageUrls
array to display a set of dots. Note that here, we have passed the index as the key. In this case, it is okay to pass the index as key since the imageUrls
is a constant array that won't ever change.
- Next, we will add an
onClick
handler for each dot. We can pass the index of the corresponding dot as a parameter to the setCurrentIndex
function. Here, as we need to pass the index parameter to the event handler, we need to define the event handler as an inline function.
- Finally, to highlight the position of the current image, we'll set a black background for the selected dot. We can achieve this by comparing the state variable
currentIndex
with the index of each dot and applying the class name neeto-ui-bg-black
conditionally:
However, we can avoid the hassle of writing the conditions as above by leveraging the classNames package to apply classnames conditionally. This package provides a function called classNames
that facilitates the conditional joining of class names.
To install classnames
, run the following command:
With the classNames
function, we pass the classnames to include and the corresponding conditions as an object. In addition, the class names that should always be present are passed as a string.
Here is what the code will look like with the classNames
package. Much better, right?
Hooray 🎉 ! We have learned the concepts of state and event handling in React and implemented a simple Carousel
component.
Let's commit the new changes:
You can verify the changes here.