React Hooks: Building a simple like-dislike review system

This will be a basic tutorial to familiarize the concept of state hook.

Photo by Ferenc Almasi on Unsplash

The idea

The mini-application will display videos of four rock bands and ask you to like or dislike the bands accordingly. A list of all your liked bands will be displayed below.

The environment

I always like to make a small snippet of code on codesandbox to try out things as a proof of concept.

Steps

  1. Create an account at codesandbox.
  2. Open the dashboard, and Click on “Create React Sandbox”.
  3. Let the magic happen!

The data

In most of the cases, the data is obtained through an API request. For this tutorial we will be using locally stored data.

The file is saved as public/bands.json. It contains an array of objects having name, the top song, its YouTube video ID of my favorite rock bands.

[
{
"name": "Linkin Park",
"song": "In the End",
"youtubeId": "eVTXPUF4Oz4"
},
{
"name": "Breaking Benjamin",
"song": "Diary of Jane",
"youtubeId": "DWaB4PXCwFU"
},
{
"name": "Starset",
"song": "My Demons",
"youtubeId": "p-N_y1bZtRw"
},
{
"name": "Thousand Foot Krutch",
"song": "War of Change",
"youtubeId": "HdnTSXUWd3E"
}
]

The code

src/App.js

The code begins with our main App component. Hooks can only be used with functional components.

import React from "react";
import "./styles.css";
import bands from "../public/bands.json";
import Rating from "./Rating";
export default function App() {
const [likedBands, updateLikedBands] = React.useState([]);
return (
<div className="App">
<h1>Rate these bands</h1>
{bands.map(band => (
<Rating
key={band.youtubeId}
band={band}
updateLikedBands={updateLikedBands}
likedBands={likedBands}
/>
))}
<h2>Liked bands</h2>
<ul>
{likedBands.map(name => (
<li>{name}</li>
))}
</ul>
</div>
);
}

useState is a special function (from now on a Hook) that lets you add React state to function components.

const [likedBands, updateLikedBands] = React.useState([]);

Here we declare a new state variable. These variables are preserved as long as the concerned component is not re-rendered.

The argument passed to useState is to assign an initial state. In our case, we provide an empty array because the user has not yet chosen to vote.

useState returns a pair of values, the current state, and a function that updates it. We use array destructuring to get the two values. In our case, likedBands denotes the bands liked at any instant, updateLikedBands help us update the likedBands.

{bands.map(band => (
<Rating
key={band.youtubeId}
band={band}
updateLikedBands={updateLikedBands}
likedBands={likedBands}
/>
))}

Here, we loop through all the band objects and send them to the child component Rating.

What are those properties/ (props) for?

  • key: an unique ID to denote different components.
  • band: the band object containing name, song, and youtubeID.
  • updateLikedBands: to allow the child to update preference of each band.
  • likedBands: to check for duplicates while inserting or deleting liked bands.

As the child components updates the users preferred bands, the list keeps on displaying the updated array of likeBands.

Photo by Grzegorz Walczak on Unsplash

Too much to grasp? Re-read the above explanation for the code.

src/Rating.jsx

import React, { useState } from "react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faThumbsUp, faThumbsDown } from "@fortawesome/free-solid-svg-icons";
const Rating = props => {
const { name, song, youtubeId } = props.band;
const [isLiked, updateLike] = useState(false);
const handleLike = () => {
let currentLikedBands = props.likedBands;
if (!isLiked) {
updateLike(true);
if (!currentLikedBands.includes(name))
props.updateLikedBands(
[...currentLikedBands, name]
);
} else {
updateLike(false);
if (currentLikedBands.includes(name))
props.updateLikedBands(
currentLikedBands
.filter(band => band !== name)
);
}
};
return (
<div>
<iframe
title={name}
width="420"
height="315"
src={`https://www.youtube.com/embed/${youtubeId}`}
/>
<div
style={{
paddingBottom: 10,
paddingTop: 10
}}
>
<button
onClick={handleLike}
disabled={isLiked}
>
<FontAwesomeIcon
icon={faThumbsUp}
style={{ paddingRight: 5 }}
/>
</button>
<button
onClick={handleLike}
disabled={!isLiked}
>
<FontAwesomeIcon
icon={faThumbsDown}
style={{ paddingLeft: 5 }}
/>
</button>
</div>
<p>You {isLiked ? "liked" : "disliked"} </p>
<h3>
{song} by {name}
</h3>
<hr />
</div>
);
};
export default Rating;

Unlike App, here we use arrow functions to create the functional component. But why? No specific reason, just to make you familiar with alternate syntax.

const { name, song, youtubeId } = props.band;

The above code destructures the object band to its constituent properties.

const [isLiked, updateLike] = useState(false);

Another state hook! Notice here we are using a boolean value false . Before the user has voted anything, we assume that he/she dislikes the band.

Unlike the state management in class components, states here can be any primitive value, array, or object but not necessarily only object.

What’s going on inside JSX?

<div>
<iframe
title={name}
width="420"
height="315"
src={`https://www.youtube.com/embed/${youtubeId}`}
/>
<div
style={{
paddingBottom: 10,
paddingTop: 10
}}
>
<button
onClick={handleLike}
disabled={isLiked}>
<FontAwesomeIcon
icon={faThumbsUp}
style={{ paddingRight: 5 }}
/>
</button>
<button
onClick={handleLike}
disabled={!isLiked}>
<FontAwesomeIcon
icon={faThumbsDown}
style={{ paddingLeft: 5 }}
/>
</button>
</div>
<p>You {isLiked ? "liked" : "disliked"} </p>
<h3>{song} by {name}</h3>
<hr />
</div>
  • iframe helps us to embed the youtube videos. We dynamically load the URLs (links) with the respective youtubeID.
  • We have created a small div to contain the like dislike buttons. The icon buttons are taken from fontawesome package. Minimal styling has been added to make the UI presentable.
  • Text elements p and h3 are used to display whether the user like or disliked the song by that respective band.

Are we skipping something?

Yes, what’s handleLike()?

const handleLike = () => {
let currentLikedBands = props.likedBands;
if (!isLiked) {
updateLike(true);
if (!currentLikedBands.includes(name))
props.updateLikedBands(
[...currentLikedBands, name]
);
} else {
updateLike(false);
if (currentLikedBands.includes(name))
props.updateLikedBands(
currentLikedBands
.filter(band => band !== name)
);
}
};

This is an arrow function.

If the current song was not liked and the user likes it now,

  • it updates the state of isLiked of that song to true
  • if the band name is already not there in the likedBands array, the band name is added.
  • Spread operator is used to add the band name.

If the current song was liked and the user dislikes it now,

  • it updates the state of isLiked of that song to false
  • if the band name is already there in the likedBands array, the band name is deleted.
  • Filter function is used to iterate and delete that particular band name.

Yay! We reached the end of the tutorial!

The result

Do you see a mistake in there? Mention in the comments.

Did you like it? Show your support through claps 👏.

Follow me and stay tuned for further articles.

I’m Srinjoy Santra. An aspiring web developer. You can connect with me on LinkedIn, visit my Github account, or follow me on Twitter.

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store