I was sitting behind my computer thinking of creating a small application that would leverage some advanced JavaScript concepts.. in particular I was thinking of real world implementations of throttle
and debounce
.
I've created an application that kind of leverages the logic of throttle
but will also showcase some difficult to maneuver web api's such as setTimeout
and setInterval
if this has piqued your interest.. please read on.
TLDR
for those of you that don't like to read.. here is the logic for the application
const GetScores = () => {
const [homeScore, setHomeScore] = useState(0);
const [awayScore, setAwayScore] = useState(0);
const updateScore = () => {
const scoreOptions = [0, 2, 3];
const randomNum = () => Math.floor(Math.random() * 3);
setHomeScore(homeScore + scoreOptions[randomNum()]);
setAwayScore(awayScore + scoreOptions[randomNum()]);
};
return {
homeScore,
awayScore,
updateScore,
};
};
const usePlayGame = ({
team1 = "https://cdn.mos.cms.futurecdn.net/8227s5kMEx84YEHWLbxCb4-1200-80.jpg",
team2 = "https://www.dailynews.com/wp-content/uploads/migration/2015/201506/SPORTS_150619439_AR_0_FHMRLLUSDJPX.jpg?w=1024",
timeLapse,
delay,
}) => {
const [time, setTime] = useState(15);
const [pause, setPause] = useState(false);
const [quarter, setQuarter] = useState(0);
const [winner, setWinner] = useState("");
const { updateScore, homeScore, awayScore, homeTeam, awayTeam } = GetScores(
team1,
team2
);
useEffect(() => {
// if we have finished the last quarter
if (quarter >= 3) {
// we decide the winner by checking the scores
const winner = homeScore > awayScore ? homeTeam : awayTeam;
// we set the winner
setWinner(winner);
// we pause the game indefinitely
return setPause(true);
}
// Let's unpause after a break (setTimeout callback)
if (pause) {
setPause(false);
}
// on every interval increase time by the timelapse set (default 1000 ms)
const interval = setInterval(() => {
updateScore();
setTime(time - 1);
}, timeLapse);
// if the time is run out and the game is not yet over
if (time === 0 && quarter !== 3) {
// We pause the match
setPause(true);
// we move to the next quarter
setQuarter(quarter + 1);
// we reset the time after a short break (delay default 5000 ms)
setTimeout(() => {
setTime(time + 15);
}, delay);
return clearInterval(interval);
}
return () => clearInterval(interval);
}, [time]);
return {
time,
pause,
quarter,
winner,
homeScore,
awayScore,
homeTeam,
awayTeam,
};
};
Fantasy Basketball
for the implementation of the logic I wanted to create something fun. Something that I've started enjoying a lot... Basketball. Go Warriors!! am I right.
I have decided to create a fantasy basketball application that can run an entire game as soon as the page loads. There are a lot of moving parts to this application, but not to worry. we will build out every piece individually. The following we will need:
- a timer
- a scoreboard
- breaks after every quarter
- deciding a winner
Prerequisites
This tutorial will be build in React with JavaScript, a basic understanding of both would definitely come in handy for this tutorial just to understand some of the more complex pieces of the application.
we will be installing one more dependency.. styled components.
npm install --save-dev styled-components
not a necessity, just very powerful in my opinion and comes in handy in almost every project. It is a library to turn your css into React components where you can also pass along properties to it to manipulate the css.
Getting started
to get started with any react project for home use, we can simple use Create React App (CRA). Create React app is a powerful tool to bootstrap a React project and the perfect solution for building simple applications like this.
Run the following command to get started.
npx create-react-app ${your app name here}
then type the following command into your CLI cd ${your app name here}
and voila. we have a project setup.
Structure
Below you can see the structure of the game.
I will be going over every component as we move along.
Recreate the structure as you see fit, if you want to follow along with me then recreate the folders as shown in the image above
API
the first component I would like to build is the API. In this tutorial I'm not actually using an API, I have just created a hook that runs locally but basically mocks an API call.
this logic is stored in src/Services/api.js
first let's start with what we want this API to return.
We want the following:
- Home team score
- Away team score
- function to update the score
import { useState } from "react";
export const GetScores = (
) => {
const [homeScore, setHomeScore] = useState(0);
const [awayScore, setAwayScore] = useState(0);
const updateScore = () => {
const scoreOptions = [0, 2, 3];
const randomNum = () => Math.floor(Math.random() * 3);
setHomeScore(homeScore + scoreOptions[randomNum()]);
setAwayScore(awayScore + scoreOptions[randomNum()]);
};
return {
homeScore,
awayScore,
updateScore,
};
};
to keep track of the scores I created to state variables that can keep track of each teams scores independently.
Next I needed a function that would update the score whenever we needed it to. The updateScore
function does just that. Every second we run the play, the updateScore
function runs on each of the teams.
I've created an array with the possible scores in a basketball game being: 0, 2 and 3. You can dunk, layup or shoot to get 2 points and you can shoot from the 3 point line to get 3 points, your only other option is missing.
I then needed something to determine which one of the three options it would be. I created a function that returns Math.floor(Math.random()*3)
. Let's break that down.
Math.floor takes out decimal values and "floors" them so brings it basically removes the decimals (e.g. 10.8 is 10)
Math.random will give us a random numerical value between 0 and 1 (e.g 0.245 or 0.678) we multiply this with the amount of options we have (3) and we will receive a random number between 0 and 2 which corresponds with the indexes in our array.
then we set the new score by adding up the old score with the randomly selected outcome and return those.
Hooks
Now it is time to get to the nitty gritty. I've created a custom hook to take care of all the logic pertaining to the actual game. This way I keep my UI component clean of too much logic and make it easier to read.
this logic is stored in src/Services/hooks.js
import { useState, useEffect } from "react";
import { GetScores } from "./api";
const usePlayGame = ({ team1, team2, timeLapse = 1000, delay = 5000 }) => {
const [time, setTime] = useState(15);
const [pause, setPause] = useState(false);
const [quarter, setQuarter] = useState(0);
const [winner, setWinner] = useState("");
const { updateScore, homeScore, awayScore } = GetScores();
useEffect(() => {
// if we have finished the last quarter
if (quarter >= 3) {
// we decide the winner by checking the scores
const winner = homeScore > awayScore ? team1 : team2;
// we set the winner
setWinner(winner);
// we pause the game indefinitely
return setPause(true);
}
// Let's unpause after a break (setTimeout callback)
if (pause) {
setPause(false);
}
// on every interval increase time by the timelapse set (default 1000 ms)
const interval = setInterval(() => {
updateScore();
setTime(time - 1);
}, timeLapse);
// if the time is run out and the game is not yet over
if (time === 0 && quarter !== 3) {
// We pause the match
setPause(true);
// we move to the next quarter
setQuarter(quarter + 1);
// we reset the time after a short break (delay default 5000 ms)
setTimeout(() => {
setTime(time + 15);
}, delay);
return clearInterval(interval);
}
return () => clearInterval(interval);
}, [time]);
return {
time,
pause,
quarter,
winner,
homeScore,
awayScore,
};
};
export default usePlayGame;
Let me break this badboy down. Let's start with the useEffect
and I'll break down what each part of the hook will do.
// if we have finished the last quarter
if (quarter >= 3) {
// we decide the winner by checking the scores
const winner = homeScore > awayScore ? team1 : team2;
// we set the winner
setWinner(winner);
// we pause the game indefinitely
return setPause(true);
}
This if statement is basically a principal taken from recursive programming. When we reach the final quarter we want the useEffect to no longer run so we return with setPause
to true.
Recursive programming is where we call a function inside of itself and have an exit after we have reached a certain point.
let count = 0;
const addTo10 = () => {
if(count === 10) return
console.log(count)
addTo10(count++)
}
as you can see with this simple example, we have a count that starts off at 0. The addTo10 function calls itself with count++
adding up the count every time it runs. The if statement in the beginning makes sure that when the count equals 10 we return and the function stops running.
you can copy and paste this into a browser to see how it works.
the next code block that is important is this
// on every interval increase time by the timelapse set (default 1000 ms)
const interval = setInterval(() => {
updateScore();
setTime(time - 1);
}, timeLapse);
we create a timer by using setInterval
which reruns a function after every interval. The timeLapse
variable is set to 1000ms in the above example which accounts for a single second. so we decrease a number value by 1 every time it runs. This is our clock.
on the interval we update the score which is the function we get from our API we created before:
const { updateScore, homeScore, awayScore } = GetScores();
this will update the score and export the homeScore and awayScore for the respective teams.
// if the time is run out and the game is not yet over
if (time === 0 && quarter !== 3) {
// We pause the match
setPause(true);
// we move to the next quarter
setQuarter(quarter + 1);
// we reset the time after a short break (delay default 5000 ms)
setTimeout(() => {
setTime(time + 15);
}, delay);
return clearInterval(interval);
}
return () => clearInterval(interval);
}, [time]);
this is the main code. Let's go through it step by step. if the time hits zero and it isn't the final quarter we want to set pause to true, increase the quarter and use a setTimeout
callback to reset the timer back to 15 and use a delay
variable to set the milliseconds we want to wait (default 5000 ms or 5 sec). within useEffect we will have to return the clearInterval to stop the intervals from running.
after this has run we will have to setPause
to false to continue our loop
if(pause){
setPause(false)
}
Throttle
if you look up the principle of throttle in JavaScript it is usually has similar code as the one above. we want to set a variable or state to true so a function can run, inside the function we set the state to false to make it stop running and use a setTimeout
to start the function again. In this case setInterval
is taking care of resetting the state.
below you can see an example of throttling in JavaScript
//initialize throttlePause variable outside throttle function
let throttlePause;
const throttle = (callback, time) => {
//don't run the function if throttlePause is true
if (throttlePause) return;
//set throttlePause to true after the if condition. This allows the function to be run once
throttlePause = true;
//setTimeout runs the callback within the specified time
setTimeout(() => {
callback();
//throttlePause is set to false once the function has been called, allowing the throttle function to loop
throttlePause = false;
}, time);
};
UI
now for the fun part. We can build out the UI. The UI is build out into several components, the main one being ScoreCard
import React from "react";
import Winner from "../Winner";
import { CardWrapper, Score, Teams, TeamLogo } from "./styles";
import usePlayGame from "../../Services/hooks";
import Time from "../Time";
import Break from "../Break";
import { game } from "./utils";
const ScoreCard = ({
team1 = "https://cdn.mos.cms.futurecdn.net/8227s5kMEx84YEHWLbxCb4-1200-80.jpg",
team2 = "https://www.dailynews.com/wp-content/uploads/migration/2015/201506/SPORTS_150619439_AR_0_FHMRLLUSDJPX.jpg?w=1024",
timeLapse,
delay,
}) => {
const { quarter, time, homeScore, awayScore, pause, winner } = usePlayGame({
team1,
team2,
timeLapse,
delay,
});
return (
<CardWrapper>
<Teams>
<TeamLogo src={team1} /> vs
<TeamLogo src={team2} />
</Teams>
{game[quarter]}
<Score>
{homeScore} - {awayScore}
</Score>
{pause ? <Break quarter={quarter} /> : <Time time={time} />}
<br />
{winner && <Winner img={winner} />}
</CardWrapper>
);
};
export default ScoreCard;
The scorecard has the teams
<Teams>
<TeamLogo src={team1} /> vs
<TeamLogo src={team2} />
</Teams>
the quarter we are in which utilizes a utils.js
file. I've combined both in the section below
{game[quarter]}
export const game = ["First Half", "Second Half", "Third Half", "Full Time"];
it selects the correct game by using the quarter which runs from 0 to 3 to decide what part of the game we are in.
the next part is the score
<Score>
{homeScore} - {awayScore}
</Score>
which receives the homeScore and awayScore from our hooks.js
file
then we have a couple of operators that use the Break
, Time
and Winner
components
{pause ? <Break quarter={quarter} /> : <Time time={time} />}
<br />
{winner && <Winner img={winner} />}
if we are on a pause we want to showcase the word 'break' except for when we reach full time. otherwise we want to showcase the time but because time runs from 15 to 0, we want to showcase it as 15, 14, 13, 12, 11, 10, 09, 08, 07, 06, 05, 04, 03, 02, 01, 00.
for those reasons I've build out the following components in the Break
folder and the Time
folder respectively.
//Break
import React from "react";
const Break = ({ quarter }) => {
console.log(quarter);
return <div>{quarter < 3 ? "Break" : ""}</div>;
};
export default Break;
//Time
import React from "react";
const Time = ({ time }) => <div>{time < 10 ? `0${time}` : time}</div>;
export default Time;
the final piece of the puzzle is the Winner component
import React from "react";
import { WinnerCard } from "./styles";
const Winner = ({ img }) => {
return (
<div>
<h1>!! Winner !!</h1>
<WinnerCard src={img} />
</div>
);
};
export default Winner;
Styled components
for the ScoreCard
component and the Winner
component I have created the following styles using styled components:
//ScoreCard
import styled from "styled-components";
export const CardWrapper = styled.div`
display: flex;
flex-direction: column;
text-align: center;
`;
export const Score = styled.div`
display: flex;
font-size: 8rem;
justify-content: center;
`;
export const Teams = styled.div`
display: flex;
justify-content: space-between;
align-items: center;
font-size: 3rem;
font-weight: bold;
gap: 1rem;
padding: 15px;
border-bottom: 10px double black;
`;
export const TeamLogo = styled.img`
width: 200px;
height: 115px;
border-radius: 15px;
padding: 10px;
border: 10px double black;
`;
export const Winner = styled.div`
display: flex;
flex-direction: column;
`;
//Winner
import styled from "styled-components";
export const WinnerCard = styled.img`
width: 200px;
height: 115px;
padding: 10px;
border: 5px double black;
border-image: linear-gradient(to top right, #a67c00 10%, #fabf00 90%) 1 round;
`;
these you can import into your file with named exports from './styles' like so
//ScoreCard
import { CardWrapper, Score, Teams, TeamLogo } from "./styles";
//Winner
import { WinnerCard } from "./styles";
Extra Curricular
for those of you interested in the power of styled components I have added a styled component that I have used on my App.js to set the layout for the page
import styled from "styled-components";
export const Direction = styled.div`
${({ direction }) => {
return direction === "row"
? `{
display: flex;
align-items: center;
gap: 1rem;
padding: 1rem;
margin: auto 0;
overflow: scroll;
max-width: 100vw;
}`
: `{
display: grid;
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
grid-gap: 1rem;
overflow: scroll;
}`;
}}
`;
this component takes in a direction, if that direction is 'row' then the first part of the ternary is used on our wrapper component to set all of our child components in a single row. If you decide not to use that but go for 'column' or anything else for that matter instead, the flow will be done in a grid layout that is responsive.
App.js
Below you will find my implementation of the ScoreCard in our App.js file.
import { Direction } from "./styles";
import ScoreCard from "./Components/ScoreCard";
function App() {
return (
<Direction direction="row">
<ScoreCard />
</Direction>
);
}
export default App;
Thank You
I hope this helped make sense of setInterval
, setTimeout
, throttle and recursion. It was a lot of fun to build and if you guys have questions I would love to hear them from you.
Please leave a like ❤️, leave a comment 📃 or connect 👋 :
Here is a link to the Github Repo: Sports App
Top comments (4)
It would be nice if you added the link to the deployed application and the project repo on GitHub.
Nice 👍
Just added it at the bottom of the article
@geesilu 🤩
wow🤩