Suparth Narayan Ghimire
[email protected]
977-9848952466 🇳🇵
Kathmandu, Nepal
Skills and Abilities
- Excellent Proficiency in NodeJS and Express to create server side applications using
REST APIs
- Proficiency in
TypeScript
- Caching using
Redis
- Experience with Domain Management with
Cloudflare
- Great knowledge of Client Side Libraries and Frameworks -
TailWindCSS
,Mantine UI
,Bootstrap
,React
,Next.js
, - Experiece in working with version control -
Git/GitHub
- Worked on Multiple Projects using databases such as
MySQL
,ProstGreSQL
,MongoDB
,Firebase
- Experience in
Web Sockets
usingSocketIO
- Knowledge of Devops:
Docker
,NGINX
,Google Cloud
- Good Understanding of
C++
Work Experience
-
Sr. Full Stack Developer
March 2023 - Current
Swivt Techologies -
NDA Issues.
-
Project Lead - The Wadi Tribe Mobile Application
July 2022 - November 2022
Freelance- Managing Team of 4 along with an International Client to create a Full Stack Application with Augmented Reality
- Implemented fully featured REST API in Microservice Architecture with CRUD, Authentication and Authorization in Node JS using TypeScript and MySQL with Prisma ORM
- Created Robust Admin Panel to fulfill neads of current and future projects in Next JS using TypeScript with TailwindCSS and MantineUI
-
Design System in Next.js
March 2022 - July 2022
Aspark Systems- Created Design System for a Educational Platform using custom CSS designs and React Bootstrap
- Integrated Existing REST API to design components
-
Front End Application in Next JS
December 2021 - February 2022
Aspark Systems- Integrated Existing REST API with frontend design for company's long term client
- Created Multiple Resuable Components in the Application along with using preexisting templates.
Projects and Community
-
Chess
Game of Chess in Browser (On Going) -
Talk
End to End Encrypted Chat Application -
ts-template
CLI Tool
Education
- Tribhuvan University - BscCSIT 2019 - Current
You Don't Understand Statements (in JavaScript)
You think you do, but you dont.
What?
From the day we started to learn programming, few words are fed to us. Variables
, constants
, functions
, classes
, statements
etc. All of them are very easy to understand, but something about statements
always seem confusing. It is not something that needs to be understood completely to be able to write a program though! I mean we can all write a simple program to add two numbers in programming language of choice right? Even further, we can create a calculator application using if
and switch
statements
along with arithmetic operators. Then what is all the fuss about? Why am I writing this blog? It might feel redundant at first, but believe me when I say you don't fully understand the meaning behind statements
in low level computing (what compiler understands).
Why?
Why is this important? Why am I reading this blog? Why should I care about it when I can create a server to create REST APIs and an entire full stack application? I know how control statements
work. What is your point?
Statements
are fundamental building blocks that make or break a programming language. Knowing how something works does not mean you fully understand what it means. Many of us are not privy to the concept of how compilers work, and how the code we write gets compiled. The legend Linus Torvalds once said "If you think like a computer, writing C makes sense
" and "When I read C, I know what the assembly language will look like
"
Here is the video where he says this
You can intrepret this in anyway you like, but what I understand is writing code in a language means you need to understand what it is going on underneath. Knowing how to do something to make it work isn't enough.
Point?
Saying this, I know many of us already know the meaning of statements and how they work in respective programming language of choice. But I did a poll recently in an IT Community - ITSNP
(its a great community. Join Here if you have not already)
The question was
- Which of these is "objectively" the correct syntax of if statement in JavaScript?
if (condition) statement;
if (condition) { statement; }
if(condition) { statement 1; statement 2; ... }
Answer it before moving further to see if you were correct.
Poll Result
This was the result of the Poll.
Before providing the answer to the question of the poll lets understand what statement
means and how do they work.
Statements
We have all heard that variables/operators/functions make an expression and expressions make up statements.
Example:
a + b;
is an expression and
c = a + b;
is a statement
This shows that the common syntax for any sequence of characters to be a statement is
Generalization
variable = expression;
Okay. But we also have if
statements, for
statement, while
statement? What are those? They don't follow this syntax. Are they not statements? They are. That is why this traditional definition of statement
is not correct.
Correction
The correct definition of statement would be:
A command given to the computer to instruct it to perform some action.
The thing to note here is the phrase instruct it perform some action
.
Not task
, but action
. If you replace the word action
by task
then the definition:
A command given to the computer to instruct it to perform some task.
would define a program
.
Action?
Action can be anything.
- Perform arithmetic operation
- Check for boolean logic
- Jump to some other part of program etc.
These actions all are classified into different types in different programming languages.
In JavaScript some of them are as follows:
- Expression Statement -> (all statements ending with semicolon ";")
- Jump Statement -> (Function Calls)
- Labeled Statement -> (Switch Case)
- Selection Statement -> (if else statements)
However, compiler understands statements by generalizing them in 2 different types.
- Simple Statement: Single Statement
Single Statements are usually written in a single line
Example:
let y = 3 + 10;
The next one is Compound Statement, which we will discuss after a while.
Statements in the Madness of CFG - Compiler Design
Note: to avoid confusion I am not writing the examples of CFG here in the preffered convention, but as simple single Uppercase characters
A compiler reads statement as a syntax. The syntax of a compiler is understood as a grammer. The grammer is known as Context Free Grammer
or CFG
.
A CFG
for representing syntax for simple arithmetic statement can be written in this way
S -> id = E
E -> E + E | E - E | E * E | E / E | (E) | id
These are read as
S
producesid = E
- E produces
E + E
orE - E
orE * E
orE / E
or(E)
orid
This only is a simple representation. This does not represent everything
Here id
is a terminal symbol
(assume a variables for now)
S
and E
are non terminal symbols
. S
means Syntax and E
means Expression who are recursive in nature.
WTF?
I know this is confusing, but bear with me for a moment
Imagine this being a recursive function, where the production of terminal symbom (id
) is the base case, and production of non terminal symbols (S
and E
) are recursive case.
Say a compiler has to parse this arithmetic statement
x = a + b - a / b;
Then a syntax tree is formed this way
Breakdown | Reason
-----------------------------------------------------------------------
S -> id = E | (Production of S)
rm -> id = E - E | (E produces E - E -> recursive breaking)
rm -> id = E - (E) | (E produces (E) -> recursive breaking)
rm -> id = E - (E / E) | (E produces E / E -> recursive breaking)
rm -> id = E - (E/id) | (E produces id -> recursive breaking)
rm -> id = E - (id/id) | (E produces id -> recursive breaking)
rm -> id = E + E - (id/id) | (E produces E + E -> recursive breaking)
rm -> id = E + id - (id/id) | (E produces id -> recursive breaking)
rm -> id = id + id - (id/id) | (E produces id -> recursive breaking)
rm means right most
the end of this recursive definition resulted in all production reaching a terminal symbol. This means the statement
x = a + b - a / b;
is correct.
This creates a syntax tree that looks like this
If you see closely, this approach is similar to solving a simplification problem using BODMAS rule. This is how compiler parses an arithmetic statement.
If you want to learn more about Context Free Grammers, here is a link to series of videos along with written material by Daniel Shiffman
. Contect Free Grammer
I know this is confusing and it might seem like I am going off topic, but giving you context before jumping forwards was necessary.
Compound Statements
We discussed the syntax of a simple statement above right. Then what would be the syntax for a compound one?
First we need to change the way we define a simple statement, not as simple expression but a sentence of logic such as
- It is raining
- It is cold
etc.
These are simple statements in logic, which are represented by
- p: It is raining
- q: It is cold
Compound statements then can be created by combining them using conjunctive words like and, or, if..then
Example:
Description | Symbolic | Statement |
---|---|---|
p and q | p V q | It is raining and it is cold |
p or q | p ^ q | It is raining or it is cold |
This shows that compound statements dont need to be in multiple lines. Compound statements mean combination of simple statements. Simple statements in multiple lines are called Collection of multiple statements. Collection of multiple simple statements are one of the production of compound statements.
Hence, c = a || b
, d = a && b
, etc. they all are compound statements as they combine two or more simple statements.
A question then, are a + b, a - b compound statements?
No. Why? These operators donot combine two statements, but they provide a single answer by doing an operation.
Answering the Mystery
Description | Symbolic | Statement |
---|---|---|
p implies q | p => q | If it is raining then it is cold |
We see that if statements ar a compound statements as they comprise of multiple simple statements.
Hence, in JavaScript, if we had to create a production for if statement syntax, we would create it this way
Using proper naming convention for CFG here
<stmt> -> if(<expr>) <stmt>
<stmt> -> <compound_stmt>
<compound_stmt> -> { <stmt_list> }
<stmt_list> -> <stmt> <stmt_list> | epsilon
The answer
Compilers read code from a *.js
file. They all are bunch of characters written together. We programmers write code in a way that the compiler understands. There are certain predefined structure to write a syntax for alot of statements. If
being one of them.
The syntax above is how if
statement is defined in JavaScript. What does that mean?
It means that whenever it encounters the word if, an opening parenthesis must follow it. There must be an expression
following it. Expression here is a non terminal symbol, thus it's production is defined somewhere else. After expression ends, a closing parenthesis must follow.
The next thing we see is a non terminal symbol called stmt
, not a opening curly brace. Stmt
is a non terminal symbol which then yields a compound statement. This then yields another production that has a curly brace in front. However, stmt
doesn't only yield a compound
statement.
Here are some that is produced by stmt
<stmt> -> id = <expr>;
<stmt> -> if (<expr>) <stmt>
<stmt> -> while (<expr>) <stmt>
<stmt> -> do (<expr>) <stmt>
<stmt> -> for (<stmt> <expr>; <expr>) <stmt>
<stmt> -> return <expr>;
And more. This is why these pieces of code are valid in JavaScript
if(true)
if(true)
if(true)
let x = 7 + 10;
This is possible as this is parsed as
Breakdown | Reason
<stmt> -> if(<expr>) <stmt> | <stmt> is broken down to if(<expr>) <stmt>
rm -> if(<expr>) if(<expr>) <stmt> | Same as Above
rm -> if(<expr>) if(<expr>) if(<stmt>) <stmt> | Same as Above
rm -> if(<expr>) if(<expr>) if(<stmt>) id = <expr>; | <stmt> is broken down to id = <expr>;
let i = 10;
if (true) while (i < 10) if (true) i++;
let i = 10;
if (true) while (i < 10) return true;
let i = 10;
if (true) {
let z = i;
i = i + 10;
}
Try to decode others by parsing them yourself.
Alternate Universe
Let's see if the production was abit different
If the syntax for if was this way
<stmt> -> if(<expr>) {<stmt_list>}
<stmt_list> -> <stmt> <stmt_list> | epsilon
Then only code that would be parsed would be
if (true) {
return true;
}
This would be a valid JavaScript code
However,
if (true) return false;
This would return in syntax error.
I hope you got your answer at this point. The correct answer if the question of the poll is ... Drum Roll...
if (condition) statement;
Replacing condition with expression would be more correct as
if (6 + 7) console.log("This is valid");
is also a valid code in JavaScript, but you get the point.
Off!
This was a long one. I hope you now understand why statements are important and the complexity behind designing a statement in a programming language is very high.
Fun fact, compound statements are called block statements in JavaScript. This is where the infamouse block scope
word comes from. What does that mean? Well, stay tuned as the next blog is all about scoping in JavaScript.
Leaving Statements
That is all I have in store for today. Stay tuned for upcomming blogs in the future.
Adios.
What is This? (in JavaScript)
We all developers have atleast once in our lifetime encountered the this
keyword in many Programming Languages (self
if you are from Python
island). The implementation of this
varies somewhat in all these languages, but it is not as confusing as it is in JavaScript.
Detour to C++
Before starting, lets see another famous programming language that utilizes the this
keyword, C++
.
The example here is true for most other languages as well
I think the implementation of this
in C++
is as straight forward as it gets. It represents the current instance of the Class.
Let's See an Example
#include<iostream>
class Person{
public:
std::string name;
Person(std::string name){
this->name = name;
}
};
int main(){
Person p = Person("Suparth");
std::cout << p.name;
return 0;
}
Here, we see that parameters in the constructor function have same name as the public variable names in the class. However, this
makes it clear on which variable is the value being assigned to. Here this
refers to the instance of the class.
this->name = name;
It means the value of variable name
from parameter is being assigned to the variable name
from the class instance.
Thus, while logging the value of name
from object p
, we get Suparth in terminal.
Doesn't Seem Complex does it? Well, buckle up, as we gear towards the island of JavaScript
where everything is weird. Specially This
!
Welcome to JavaScript
JavaScript
's this
has the same heart as other programming languages. It too refers to the current instance. But, the catch here is WHAT IN HELL DOES CURRENT INSTANCE MEAN?
Current Instance / Scope
JavaScript (by default) is a globally scoped language. It means you dont need to create any extra scope to execute code like in C++ or Java where you require a main
method. Here you can execute code from first line itself. It is similar to Python in this regard. To achieve this, JavaScript has scopes/contexts where everything is executed.
Scopes in JavaScript:
- Global Scope
- Function Scope
- Block Scope
We wont be going in what these scopes mean, as they are entirely different topic. Let's focus on this
for now.
Some extra topics that you can learn that are based around Scopes in JavaScript
- Hoisting
- Closures
This in JavaScript
this
refers to different things in JavaScript.
The basic gist of this
in JavaScript is that this
refers to the object of whatever scope is wrapping it. If the object of scope is not found, it refers to the parent's scope's object.
Confused? Let me elaborate.
We see that window
is an object in JavaScript which is the object that represents the global scope. Block scope doesn't have any object that represents to it. Hence, if this
is referred inside a simple block scope (within curly braces), it still refers to the window object as there is no other object for it to bind itself to.
By {}, I mean simple lexical scoping or using if statements, loops etc.
This Refering to Global Object
- Global Scope
console.log(this);
- Block Scope
if (true) {
console.log(this);
}
- Function Scope
function fn() {
console.log(this);
}
fn();
- Inside an Object
let obj = {
thisKey: this,
};
console.log(obj.thisKey);
All these examples will print the window object in console.
Everything here has been explained by the paragraph above, except for last two examples of function and the object. The obvious question here is doesn't function have its own scope? And why does this
not bind to Object?
Function:
JavaScript functions are weird as well as magical. We can not only treat them as normal subroutines by simply calling them, but also as constructors by invoking them using the new keyword! Here, function fn
has just been called like a normal subroutine. Thus, no constructor is created which does not create a function scope, but creates a local scope. And since no object corresponds to local scope, it looks at the parent scope of the function which is global scope. Since the corresponding object of global scope is window, this
inside a function invoked by a simple function call refers to the global scope or window object.
Object:
The answer to Object's example simple. Creating new object does not create any new scope, as obj
is simply a variable (even though it uses curly braces). Thus, it refers to the global scope/window object as obj
is created inside global scope.
The Prototype Carnival
Before moving forward, we must be familiar with prototypes and prototipal inheritance. I wont go much in detail, but here is a small brief.
Everything in JavaScript is an Object. If we see the prototype chain of anything in JavaScript, we see it is same as Object.Prototype
. The Prototype of Object is null. Hence, in the top level of hierarchy, we see object being present.
This means function is an object too. If you run following code:
Function.prototype.__proto__ === Object.prototype;
We get true as the result.
This Refering to Certain Object
- Functions as Constructors
function ConstructorFunction() {
console.log(this);
}
const newInvoker = new ConstructorFunction();
What is new Here?
We see that the first distinction from normal function call is we invoke the ConstructorFunction
function using new
Keyword. This now creates a function scope within the global scope. When a function scope is created, the corresponding object is Function itself (function is an Object in JavaScript - discussed above). Hence, the this
keyword will now refrence the function itself.
A more sophisticated example
function Game(initialScore) {
this.score = initialScore;
this.increaseScore = () => {
this.score++;
};
this.decreaseScore = () => {
this.score++;
};
}
const newGame = new Game(0);
console.log(newGame.score);
newGame.increaseScore();
console.log(newGame.score);
The Output is:
0
1
- Arrow Functions
const arrFunction = () => {
console.log(this);
};
const arrFnNewInvoke = new arrFunction();
This will result in an error that says arrFunction is not a constructor
.
Can you say why?
Arrow functions are not meant to be declared as constructors. Thus, they cannot be invoked using new keyword. Which means this
keyword inside arrow functions will always point to the wrapper around the arrow function. In this case, it is the global scope/window object.
- Function as Method inside an Object
let obj = {
name:"Suparth"
getName:function(){
console.log(this)
}
}
obj.getName();
Remember while discussing the global scope of this
keyword, we said that in normal function call, this
points to global object as the wrapper of the function itself is the global context? Well here the wrapper is the object obj
. What do you think will this
point to in this case?
You might think that as getName
function is wrapped by an object obj
, this
should point to global/window because Object doesnot create any scoping, which results in this
pointing to window itself.
The answer however is far more complex than that. Let's elaborate how objects are created in JavaScript.
let obj = new Object();
obj.name = "Suparth";
obj.getName = function () {
console.log(this);
};
obj.getName();
Current block of code, and the one before this results in same behavior. The only difference is how we define objects, lattar one being a longer way to do so.
Do you see what we did here though? Do you remember we invoking a function using new keyword? Yes!!! This is same as invoking a function as a constructor. This is possible because Function
is an Object
in JavaScript.
Then what will this
inside a constructor function point to? Yup. The constructor function itself! Hence the output here of obj.getName()
will print the object obj
itself.
- Arrow Function as Object Method
let obj = {
name:"Suparth"
getName:()=>{
console.log(this);
}
}
What will this
point to in this case?
If you say window object, you are absolutely correct. As arrow functions cannot act as constructor function, it wont bind to the object obj
, but to the global scope/window object.
Classes - New Kids on the Block
Es6 was an interesting transition from Es5 in JavaScript island. Many new cool kids were added in the island who brought much needded changes that made the language much more redable and accessible. We all know the introduction of Promises
and async/await
, where async/await
were the hot cakes. But well, classes were added too! So? How does this
work in Classes? Well, how it always has! Let me explain.
You see classes in JavaScript is same thing as creating functions and invoking them as constructors using the new
keyword. It just is a syntatic sugar similar to async/await
.
Thus, whatever we discussed before that includes Function
, the concept here holds true.
class Person {
constructor(p_name) {
this.name = p_name;
}
getName() {
console.log(this);
}
}
const P = new Person("Suparth");
Well, you see the similarity? Calling function as a constructor is same as creating a class! It just is an declarative way to do so. This made JavaScript more closer to other Object Oriented Languages and made inheritance
similar to other langauges as prototypes
seemed weird before.
class Game {
constructor(initialScore) {
this.score = initialScore;
}
increaseScore() {
this.score++;
}
decreaseScore() {
this.score++;
}
}
const newGame = new Game(0);
console.log(newGame.score);
newGame.increaseScore();
console.log(newGame.score);
Remember this Example from above? Well this is how we would do it using ES6 way. This proves that classes are nothing but a syntatic sugar to create more redable constructor functions.
Not Convinced? Well, run this code:
class DemoClass {}
console.log(typeof DemoClass);
The Output will be
"function"
Thus, nothing changes while using this
keyword inside classes in JavaScript.
Leaving JavaScript
That is all I have in store for today. this
isn't over by the way! this
has alot more to be explored upon. Did you know, you could modify behavior of this
in JavaScript? Well, stay tuned for more blogs, as more of this
is yet to be discussed.
Adios
Functional Programming in JavaScript and Following the DRY Principle
Introduction
We all have used JavaScript and probably have felt in love with it. If not, I don't blame you! Its not that easy of a language to work with. However, JavaScript does provide some amazing features that you can leverage to write very clean code to follow the DRY principle. One of those features is Functional Programming.
Example
We all have used multiple built in methods in JavaScript, and have felt in love with it. Lets one of them here:
1. Classic Foreach
const array = [1, 2, 3, 4, 5];
array.forEach((item) => {
console.log(item);
});
Here each item of array is iterated over once and certain operation is performed. But if we take a closer look, the actual implementation of forEach
is hidden from us. We have no idea how it is written in the spec of the language. We only know how to use it (This is called Abstraction in programming). And yet, we can pass almost any valid JavaScript statement and it does the job! How? This is the beauty of Functional Programming. We as programmers can provide a function itself that can perform certain task for us.
The alternative to this forEach
method would be a traditional for loop as
const array = [1, 2, 3, 4, 5];
for (let i = 0; i < array.length; i++) {
console.log(array[i]);
}
Note: I agree that traditional for loop
is way faster than its forEach
counterpart, but lets give that a pass it for now.
Now, what is the problem with this? Why not use a traditional for loop
and why to use the forEach
method?
Well the answer is only for better readability. However, if the amount of data to iterate over is not large, then using forEach
over traditional for loop
doesnot provide much performance boost.
How is it implemented?
Okay. Enough of the performance jibber jabber. Lets see how is it implemented.
Now, we all know that functions can be assigned to variables in javascript.
const fnName = function (...args) {
return;
};
This is a valid statement. If you dont come from a background of statically typed languages, this might seem trivial, but believe me its next to magic that we can do this. Golang
supports this btw even though it is a statically typed language, but most others donot. C/C++
, JAVA
, C#
donot support functions as variables. Infact, C#
uses delegates to do what JavaScript
does.
Your Point?
The magical thing this lets us do is pass functions as arguments to other functions!
Huh? What?
You know like any variable in JavaScript can be passed as argument to other functions like this?
function sum(a, b) {
return a + b;
}
function init() {
const aNum = 12;
const bNum = 40;
const sum = calcSum(aNum, bNum);
}
init();
Likewise, we can pass functions itself! Woah! What? Lets see an example:
function callOtherFunction(functionName) {
functionName();
}
function init() {
const functionVariable = function () {
console.log("I am being Called");
};
callOtherFunction(functionVariable);
}
init();
The output you get is
I am being Called
You see the magic? We are calling the function by passing it as a parameter to other function! It is infact breaching its lexical scope!
Okay.... So What?
The Implementation
Lets say we want to iterate over an array of data and log it to the console. Lets write a function that logs any data to console.
function LogValue(data) {
console.log(data);
}
Now, We require an array of data. Lets create one above the LogValue function
const data = [1, 2, 3, 4, 5];
const LogValue = function (data) {
console.log(data);
};
Now, we want to iterate over the data array and run the LogValue function over every item in the array thus, we can create a function that takes array and any action we wish to perform as arguments and simply iterate over the array and run the action function
const data = [1, 2, 3, 4, 5];
const LogValue = function (data) {
console.log(data);
};
function MyForEach(array, action) {
for (let i = 0; i < array.length; i++) {
action(data[i]);
}
}
Finally, lets call MyForEach function by providing data and LogValue as its arguments
const data = [1, 2, 3, 4, 5];
const LogValue = function (data) {
console.log(data);
};
function MyForEach(array, action) {
for (let i = 0; i < array.length; i++) {
action(data[i]);
}
}
MyForEach(data, LogValue);
This will give same output as this block of code
const data = [1, 2, 3, 4, 5];
data.forEach(LogValue);
If you wish to use arrow function instead, we can do this
const data = [1, 2, 3, 4, 5];
function MyForEach(array, action) {
for (let i = 0; i < array.length; i++) {
action(data[i]);
}
}
MyForEach(data, (item) => {
console.log(item);
});
This will provide same output as
const data = [1, 2, 3, 4, 5];
data.forEach((item) => {
console.log(item);
});
Now for the final show, we see that our implementation doesnot looks as good as the in built one right? I mean while using arrow function we cant even say what is going on in our code. In the native implementation we see that the code is far more readable. Well fear not! We can easily add our implementation to the existing Array method's prototype to use it in just the same way as the traditional forEach method
const data = [1, 2, 3, 4, 5];
/*
* Instead of this,
* function MyForEach(array, action){
* for(let i=0; i<array.length; i++){
* action(data[i])
* }
* }
* MyForEach(data, item=>{
* console.log(item)
* })
*/
Array.prototype.MyForEach = function (action) {
for (let i = 0; i < this.length; i++) {
action(this[i]);
}
};
data.MyForEach((item) => {
console.log(item);
});
You can use this method now to do everything that forEach
does in JavaScript!
Isnt it remarkable that you can do this? This can lead to you not repeating yourself, essentially making you follow the DRY principle.
Write a generic function once and use it wherever on your project!
Conclusion
JavaScript is filled with fun things like this that you can explore and imporove your approach in writing code. Making a habit of utilizing the language with many features that it provides will help you become a better developer.
I will leave you here with the implementation of the most useful map
method used in JS array that returns another array by performing an operation. I hope you liked this blog, and I hope this helped you get into more complex and advanced topic in JavaScript
.
The Map Method
Array.prototype.MyMap = function (action) {
const newArray = [];
for (let i = 0; i < this.length; i++) {
newArray.push(action(this[i]));
}
return newArray;
};
function init() {
const data = [4, 5, 6, 7, 8, 9];
//JS Built Implementation of Map
const jsMapAns = data.map((item) => {
return item + 5 * 3;
});
//My Implementation of Map
const myMapAns = data.MyMap((item) => {
return item + 5 * 3;
});
console.log(jsMapAns, myMapAns, jsMapAns.toString() === myMapAns.toString());
}
init();
Use Clever Math to Make Responsive Carousel
BEFORE WE BEGIN, THIS IS NOT ANOTHER NPM PACKAGE
I have used a lot of carousel components in the past, and they work good, but none of them work as expected. They often require static values when you want to make them responsive. I have decided to make my own carousel component that is responsive (on any screen size) without providing any static screen size values.
Problem
The problem that we have on hand by looking at some popular carousel components.
- React Slick Good Features, but requires custom hard coded values for responsive design.
- Mantine Carousel Good Features, but requires custom hard coded values for responsive design.
- React Responsive Carousel Good Features, but requires custom hard coded values for responsive design.
- React Multi Carousel Good Features, but requires custom hard coded values for responsive design.
- Swiper Good Features, but requires custom hard coded values for responsive design. Also, it says ReactComponent version will be deprecated in favor of web-componenjsx, which are not supported well in React yet.
See a pattern here? All of these need hard coded values to make them responsive.
All of these component packages, though they provide great deal of customization and features, require hard coded values to make them responsive.
This is something we often see in these componenjsx
const responsive = {
desktop: {
breakpoint: { max: 3000, min: 1024 },
items: 5,
slidesToSlide: 5, // optional, default to 1.
},
tablet: {
breakpoint: { max: 1024, min: 464 },
items: 3,
slidesToSlide: 3, // optional, default to 1.
},
mobile: {
breakpoint: { max: 464, min: 0 },
items: 1,
slidesToSlide: 1, // optional, default to 1.
},
};
What if I don't want to specify these values and want them to be dynamic? The major drawback of this is that the size of carousel in different screens dictates the size of inner carousel child components. This really hard to maintain.
Objective
I have one job, To make a carousel with NO HARD CODED VALUES
Demo and Tech Stack
I will be using the things that are popular at the time being to create the carousel and which probably will be used in many of your projects.
- React You can use any other libraries
- Typescript (optional)
- Tailwindcss (optional)
- Framer Motion
- Use Measure Simple hook to get width and height of our elements
Setup
-
Get the code
git clone cd carousel
-
Install Dependencies
yarn
or
npm install
-
Start the dev server
yarn dev
or
npm run dev
-
Open http://localhost:5173 to view it in the browser.
Explanation
We will be using the useMeasure
hook to get the width of our carousel. We will also be using the useTransform
hook from react-use-measure
that provides bounds for the carousel and finally, we will use framer-motion
for animation.
Lets jump into Carousel Components to see the props it takes
type PropsWithoutAutoplay = {
autoPlay?: false,
hideControls?: boolean,
batchScroll?: boolean,
};
type PropsWithAutoplay = {
autoPlay: true,
interval: number,
};
type CommonProps = {
spacing?: number,
hideControls?: boolean,
batchScroll?: boolean,
scaleOnHover?: boolean,
children: React.ReactNode,
pagination?: boolean,
};
type Props = (PropsWithoutAutoplay | PropsWithAutoplay) & CommonProps;
Most of these are self explanatory, but I will explain the ones that are not.
-
autoPlay
- If you want the carousel to auto play, set this to true. If you don't want it to auto play, set it to false or don't pass it at all. -
interval
- If you want to set the interval for auto play, pass this prop. The default value is 3000ms. -
spacing
- If you want to set the spacing between the carousel items, pass this prop. The default value is 0. -
hideControls
- If you want to hide the controls, pass this prop. The default value is false. -
batchScroll
- If you want to scroll multiple items at once, pass this prop. The default value is false. -
scaleOnHover
- If you want to scale the carousel items on hover, pass this prop. The default value is false. -
pagination
- If you want to show the pagination, pass this prop. The default value is false. -
children
- The children of the carousel. This is where you will pass the carousel items.
Lets look at the state variables for the component
const [activeCardIdx, setActiveCardIdx] = useState(0);
const [scrollAmount, setScrollAmount] = useState(0);
const [containerRef, containerBounds] = useMeasure();
const [childRef, childBounds] = useMeasure();
-
activeCardIdx
- This is the index of the active carousel item. This is used to show the active carousel item. -
scrollAmount
- This is the amount of scroll that we want to do. This is used to scroll the carousel. -
containerRef and containerBounds
- This is the ref of the container of the carousel. This is used to get the width of the container. -
childRef and childBounds
- This is the ref of the carousel item. This is used to get the width of the carousel item.
We need to convert the items in children prop into array so that we can use map
on it later on to render the carousel items.
const ITEMS = useMemo<React.ReactNode[]>(() => {
if (Array.isArray(props.children))
return props.children.map((child) => child);
else return [props.children];
}, [props.children]);
The useMemo
hook is used here to memoize the array of carousel items. This is done so that we don't have to convert the children prop into array on every render. If we don't use useMemo
, the carousel will be re-rendered on every scroll.
The next step is to setup some default values for the carousel.
const parentWidth = containerBounds.width;
const childWidth = childBounds.width;
const spacing = useMemo(() => props.spacing || 10, []);
const SCALE_FACTOR = useMemo(() => 1.1, []);
const ITEMS_IN_VIEW = useMemo(
() => Math.floor((parentWidth - spacing * 4) / (childWidth + spacing * 4)),
[parentWidth, childWidth]
);
Everything here is self explanatory. The only thing that is not is the ITEMS_IN_VIEW
variable. This is the number of carousel items that can be shown in the carousel at a time. This is calculated by dividing the width of the carousel by the width of the carousel item.
The use of useMemo
here is to memoize the value of ITEMS_IN_VIEW
. This is done so that we don't have to calculate the value of ITEMS_IN_VIEW
on every render, rather only when the value of parentWidth
or childWidth
changes.
Say the width of the parent container is 1000px
and each carousel item 's width is 200px
. Now say the spacing is 10px
. So the width of the carousel item will be
Padding Space = 2 * 10px = 20px
Gap Space = 2 * 10px = 20px
Total Space = 40px
Width of Carousel Item = 200px + 40px = 240px
Since we are using same value for Padding and Gap between the carousel items, we are multiplying the spacing by 4.
The total items, of width 240px
, that can be viewed within the container, of width 1000px
, is Math.floor(1000px/240px) = 4
. Hence container can only fit 4
items at a time.
We have initialized all the state variables and calculated the default values except for the scrollAmount
. This is because we need to calculate the scrollAmount
only when the carousel is scrolled. So we will do that in the useEffect
hook
useEffect(() => {
const current = Math.floor(activeCardIdx / ITEMS_IN_VIEW);
const prev = Math.floor((activeCardIdx - 1) / ITEMS_IN_VIEW);
const next = Math.floor((activeCardIdx + 1) / ITEMS_IN_VIEW);
let amt = 0;
if (props.batchScroll) {
if (current === prev)
amt = -(childWidth + spacing * 4) * prev * ITEMS_IN_VIEW;
else if (current === next)
amt = -(childWidth + spacing * 4) * current * ITEMS_IN_VIEW;
else
amt =
activeCardIdx >= ITEMS_IN_VIEW % activeCardIdx
? -(childWidth + spacing * 4) * activeCardIdx
: 0;
} else {
amt =
activeCardIdx >= ITEMS_IN_VIEW % activeCardIdx
? -(childWidth + spacing * 4) * activeCardIdx
: 0;
}
setScrollAmount(amt);
}, [activeCardIdx]);
The useEffect
hook is used here to calculate the scrollAmount
whenever the activeCardIdx
changes. The useEffect
hook takes a callback function as the first argument and an array of dependencies as the second argument. The callback function is called whenever the value of any of the dependencies changes.
In our case, the callback function here will be called whenever the value of activeCardIdx
changes. The activeCardIdx
changes when the user clicks on the next or previous button or when the carousel auto plays.
To change the activeCardIdx, we will use the setActiveCardIdx
function. This function takes the index of the carousel item that we want to show as the argument.
The final thing before creating our UI is to create a useEffect hook which runs the callback function on component mount; which means empty dependency array. This hook is to automatically play the carousel, this we need to set the interval for auto play.
useEffect(() => {
if (!props.autoPlay || !props.interval) return;
const selfIncTimerInterval = setInterval(() => {
setActiveCardIdx((prevCardIdx) => {
return prevCardIdx + 1 >= ITEMS.length ? 0 : prevCardIdx + 1;
});
}, props.interval);
return () => {
clearInterval(selfIncTimerInterval);
};
}, []);
The callback function here will be called on component mount. The callback function here is to set the interval for auto play. The interval is set to the value of the interval
prop. The interval
prop is the time in milliseconds after which the carousel will auto play.
We need to return a function from the callback function. This function will be called when the component unmounts. This function is used to clear the interval. This is done so that the interval is cleared when the component unmounts. If we don't clear the interval, the callback function will be called even after the component unmounts. This will cause weird behavior in the app.
Now that we have all the state variables and the useEffect hooks, we can create our UI.
return (
<div className="w-full grid items-center " ref={containerRef}>
<div className="w-full grid items-center">
<div
id="carousel-wrapper"
className={`flex overflow-x-hidden py-5`}
style={{
gap: `${spacing * 4}px`,
paddingLeft: `${spacing * 4}px`,
paddingRight: `${spacing * 4}px`,
}}
>
{ITEMS.map((item, idx) => (
<AnimatePresence key={`carousel-item-${idx}`}>
<motion.div
ref={childRef}
className="flex-1"
initial={{
scale: 1,
}}
whileHover={{
scale:
activeCardIdx === idx
? SCALE_FACTOR
: props.scaleOnHover
? SCALE_FACTOR
: 1,
}}
animate={{
scale: idx === activeCardIdx ? 1 * SCALE_FACTOR : 1,
x: scrollAmount,
}}
>
{item}
</motion.div>
</AnimatePresence>
))}
</div>
<div className="w-full grid place-items-center">
{props.pagination && (
<div className="mt-5 h-full flex gap-2">
{ITEMS.map((_, idx) => (
<button
onClick={() => {
setActiveCardIdx(idx);
}}
key={`pagination-carousel-${idx}`}
className={`w-[10px] h-[10px] ${
idx === activeCardIdx ? "bg-neutral-900" : "bg-neutral-300"
} rounded-full`}
/>
))}
</div>
)}
</div>
<>
{!props.hideControls && (
<div className="flex w-full justify-center spacing-2 w-1/5 mt-5">
<button
onClick={() => {
setActiveCardIdx((prevCardIdx) => {
return prevCardIdx - 1 < 0
? ITEMS.length - 1
: prevCardIdx - 1;
});
}}
className="p-2 w-1/5 bg-black text-white hover:bg-neutral-600 transition"
>
{"<"}
</button>
<button
onClick={() => {
setActiveCardIdx((prevCardIdx) => {
return prevCardIdx + 1 >= ITEMS.length ? 0 : prevCardIdx + 1;
});
}}
className="p-2 w-1/5 bg-black text-white hover:bg-neutral-600 transition"
>
{">"}
</button>
</div>
)}
</>
</div>
</div>
);
Lets look at the code below the actual carousel and finish it before moving on to the carousel component.
We see that if the pagination
prop is true, we render a div with the pagination buttons. The pagination buttons are rendered using the ITEMS
array. The ITEMS
array is the array of items that we pass to the carousel component. The ITEMS
array is mapped to render a button for each item. The onClick
handler of the button is set to a function which sets the activeCardIdx
to the index of the item that the button is for.
We can also see that if the hideControls
prop is false, we render a div with the next and previous buttons. The onClick
handler of the buttons is set to a function which sets the activeCardIdx
to the index of the previous or next item.
Now for the actual carousel component
We see that the carousel component has the outermost div
with the width of full parent's width and the ref
is th containerRef
. This one defines the width of the carousel. The ref
is set to the containerRef
so that we can get the width of the carousel.
The next div
is the one which contains extra items for carousel such as pagination and controls.
The next div
contains the carousel items, and its width is set to the width of the carousel. The gap
, paddingLeft
and paddingRight
are set to the value of the spacing * 4
. This is done so that the carousel items are not cut off when the carousel is scrolled.
Why 4?
We are adding spacing on 4 sides of each component. PaddingLeft
, paddingRight
and gap
. So we need to multiply the spacing by 4.
Rendering the Carousel Items
Each element in ITEMS
array is rendered by wrapping them inside two components provided by framer-motion. The first component is the AnimatePresence
component. This component is used to animate the elements when they are added or removed from the DOM. The second component is the motion.div
component. This component is used to animate the elements when they are added or removed from the DOM.
AnimatePresence
can animate the elements not only when the children components are mounted, but when they are unmounted. This means exit animations can be played when the children components are unmounted.
The motion.div
component is an addition to the html
div
element. This component can be used to animate the div
element. The motion.div
component has the initial
, whileHover
, animate
props. The initial
prop is used to set the initial state of the element. The whileHover
prop is used to set the state of the element when the element is hovered. The animate
prop is used to set the state of the element when the element is mounted.
We need to set the key of the AnimatePresence
component to carousel-item-${idx}
. This is done so that the AnimatePresence component can animate the elements when they are added or removed from the DOM.
The motion.div
component has the ref
prop set to the childRef
. This is done so that we can get the width of the carousel items.
The motion.div
component has the initial
prop set to an object with the scale
property set to 1. This is done so that the carousel items are not scaled when they are mounted.
When user hovers, if the scaleOnHover
is true, the items should be scaled by factor of SCALE_FACTOR
. But, the active item already has a scale of SCALE_FACTOR
. So, if the scaleOnHover
is true, and if the hovering item is not the active item, the item should be scaled by factor of SCALE_FACTOR
. If the scaleOnHover
is false, the item should not be scaled.
Each div should be animated by the x
and the scale
property. The x
property defines the translation of the element along the x-axis. The x
property is set to the scrollAmount
whose value is calculated using the activeCardIdx
and the width of the carousel items above inside the useEffect
hook.
The scale
property is set to the idx === activeCardIdx ? 1 * SCALE_FACTOR : 1
. This is done so that the active item is scaled by factor of SCALE_FACTOR
.
We finally set the child of motion.div
to be the actual item that we want to render.
Thats It! We got it
Now we can use the Carousel Component inside any component that we want to use it in. We can pass the items that we want to render in the carousel as the items
prop. We can also pass the pagination
prop to render the pagination buttons. We can also pass the hideControls
prop to hide the next and previous buttons. The carousel is responsive by design and will work on any screen size, or on any size of the parent's div. The only thing you need to worry is the width of the child element. The child element should be visible within the parent div.
Conclusion
We can now use the carousel component with variations like this
import React from "react";
import Card from "./components/Card";
import Carousel from "./components/Carousel";
import { ITEMS } from "./utils/mockdata";
export default function App() {
return (
<div className="flex flex-col gap-10 p-10 overflow-y-hidden">
<Wrapper>
<h1 className="font-bold text-lg px-7">Individual Scroll</h1>
<div className="w-full overflow-y-hidden">
<Carousel>
{ITEMS.map((item, idx) => (
<Card item={item} key={`carousel-default-${idx}`} />
))}
</Carousel>
</div>
</Wrapper>
<Wrapper>
<h1 className="font-bold text-lg px-7">Pagination</h1>
<div className="w-full overflow-y-hidden">
<Carousel pagination>
{ITEMS.map((item, idx) => (
<Card item={item} key={`carousel-default-${idx}`} />
))}
</Carousel>
</div>
</Wrapper>
<Wrapper>
<h1 className="font-bold text-lg px-7">Scale on Hover</h1>
<div className="w-full overflow-y-hidden">
<Carousel scaleOnHover>
{ITEMS.map((item, idx) => (
<Card item={item} key={`carousel-default-${idx}`} />
))}
</Carousel>
</div>
</Wrapper>
<Wrapper>
<h1 className="font-bold text-lg px-7">Custom Spacing</h1>
<div className="w-full overflow-y-hidden">
<Carousel spacing={20}>
{ITEMS.map((item, idx) => (
<Card item={item} key={`carousel-default-${idx}`} />
))}
</Carousel>
</div>
</Wrapper>
<Wrapper>
<h1 className="font-bold text-lg px-7">Batch Scroll</h1>
<div className="w-full overflow-y-hidden">
<Carousel batchScroll>
{ITEMS.map((item, idx) => (
<Card item={item} key={`carousel-default-${idx}`} />
))}
</Carousel>
</div>
</Wrapper>
<Wrapper>
<h1 className="font-bold text-lg px-7">Automatic Scroll</h1>
<div className="w-full overflow-y-hidden">
<Carousel pagination autoPlay interval={1000} hideControls>
{ITEMS.map((item, idx) => (
<Card item={item} key={`carousel-default-${idx}`} />
))}
</Carousel>
</div>
</Wrapper>
<Wrapper>
<h1 className="font-bold text-lg px-7">Automatic Batch Scroll</h1>
<div className="w-full overflow-y-hidden">
<Carousel batchScroll autoPlay interval={500} hideControls>
{ITEMS.map((item, idx) => (
<Card item={item} key={`carousel-default-${idx}`} />
))}
</Carousel>
</div>
</Wrapper>
</div>
);
}
function Wrapper({ children }: { children: React.ReactNode }) {
return <div className="w-full bg-neutral-200 py-5 rounded">{children}</div>;
}
That all for this tutorial. I hope you enjoyed it. If you find any issues, add them in the github repository. This is not a react component that you can npm install though. You can copy the code and use it in your project. If you want to use it as a react component, you can fork the repository and make it into a react component. I will be happy to merge your pull request.
Alright! Bye now.