React Notes1

Download as txt, pdf, or txt
Download as txt, pdf, or txt
You are on page 1of 102

SYSTEMS LIMITED => SOFTWARE HOUSE

https://www.codewithc.com/database-management-system-project-c-wxwidgets/

in case of any query post it on : forum.codewithmosh.com

FREEP!K
https://torrentgalaxy.to/

https://get.freecoursesonline.me/chatgpt-react-introduction-to-the-ai-chatbot-
revolution/

**********
TypeScript is a programming language that builds on JavaScript by adding static
types. These static types help catch errors and provide better tooling for large-
scale applications.
Static typing provides early error detection and can help prevent certain types of
runtime errors.
TypeScript is a syntactic superset of JavaScript which adds static typing.

This basically means that TypeScript adds syntax on top of JavaScript, allowing
developers to add types.

TypeScript being a "Syntactic Superset" means that it shares the same base syntax
as JavaScript, but adds something to it.

JavaScript is a loosely typed language. It can be difficult to understand what


types of data are being passed around in JavaScript.

In JavaScript, function parameters and variables don't have any information! So


developers need to look at documentation, or guess based on the implementation.

TypeScript allows specifying the types of data being passed around within the code,
and has the ability to report errors when the types don't match.

For example, TypeScript will report an error when passing a string into a function
that expects a number. JavaScript will not.

TypeScript uses compile time type checking. Which means it checks if the specified
types match before running the code, not while running the code.

Statically Typed: This means that TypeScript enforces strong typing, which requires
you to declare the data types of your variables and catch type-related errors at
compile-time. In statically typed languages like TypeScript, the data types of
variables are known and checked at the time of compilation, not during runtime.
This helps in early error detection and improves code quality and maintainability.

Superset of JavaScript: TypeScript is built on top of JavaScript. It includes all


the features of JavaScript and extends it by adding optional static typing and
additional features. This means you can write JavaScript code in TypeScript without
any modifications, but you can also enhance your code with TypeScript's type system
and other language features when needed. TypeScript code is transpiled into regular
JavaScript code that can be executed in any JavaScript environment.
In TypeScript, type checking is performed during the compilation of your code,
before it is executed in a browser or on a server. This means that the TypeScript
compiler (tsc) analyzes your code for type-related errors and issues diagnostic
messages if it finds any inconsistencies or violations of type rules.
When we say that in dynamically typed languages, the data types of variables are
determined at runtime, it means that the specific type of a variable is not fixed
when the variable is declared but is rather determined while the program is
running. Here's a more detailed explanation:

No Explicit Type Declarations: In dynamically typed languages, you typically don't


have to explicitly specify the data type of a variable when you create it. For
example, in Python or JavaScript, you can simply write:

python
Copy code
x = 5
y = "Hello, world!"
In this code, you didn't tell the language that x is an integer and y is a string.
The language figures that out on its own.

Type Determination at Runtime: The data type of a variable is determined based on


the value assigned to it. So, in the example above, x is considered an integer
because it holds the value 5, and y is considered a string because it holds the
text "Hello, world!". This determination happens while the program is executing,
i.e., at runtime.

Variable Types Can Change: In dynamically typed languages, you can change the type
of a variable during the execution of your program. For instance:

python
Copy code
x = 5
x = "I'm now a string!"
Here, x initially holds an integer value but later is assigned a string. The
language allows this flexibility.

IN JAVASCRIPT:
var x = 5;
x = "I'm now a string!";

This dynamic type determination can make coding in these languages more flexible
and concise, but it can also introduce the risk of runtime errors if you perform
operations on variables that have unexpected types. In contrast, statically typed
languages require you to declare the type of a variable explicitly, and type errors
are caught at compile-time, which can help prevent certain classes of runtime
errors.

*********** framework and library ********


When we use a framework, we can use resources to facilitate faster development and
a greater user experience. A library is used to enhance the functionality of an
application. If we develop our own library, we can use the functions in many
applications.

* library is a set of reusable functions


Framework is a reusable code that can be modified according to the need
* you are in full control when you call a method from library and the control is
then returned.

framework make request call to the code instead of calling itself in the code
: In web development, a library is a collection of pre-written functions, classes,
or code snippets that help you perform specific tasks. These tasks can include DOM
manipulation, making HTTP requests, or handling common JavaScript operations.
Libraries are typically used to solve specific problems in your web application.

In web development, a framework is a more comprehensive and structured tool that


provides a foundation for building web applications. It includes a set of rules,
conventions, and tools for building a web application, encompassing various aspects
like the architecture, routing, database interaction, and more.

BY HITESH CHAUDHRY:

library: less number of rules


a collection of codes ,,, some functions or some DOM and all such things are packed
together and being used again and again,,whenever you are making a call to the
library that is library, you have the power to call the code
framework: more number of rules
(bounded by the rules , only allowed to do certain kind of things ,,, yes you can
make changes but it will take time ,,, your framework calls your code and further
your code can call to the library

example : jquery and jango


whenever you want to make a call to jquery library you can ,,, but in jango there
is restricted environment , your template folder should be named as template
(framework is telling you to do, can change but its like digging up into settings ,
digging up into framework)

******** react ********* created by facebook in 2011


React is a free and open-source front-end JavaScript library for building dynamic
and interactive user interfaces based on components. It is maintained by Meta and a
community of individual developers and companies. React can be used to develop
single-page, mobile, or server-rendered applications with frameworks like Next.js.

* WHen our page is loaded in the browser , the browser takes the html code and make
a tree like structure call DOM (document object model).This allows us to use JS and
change the page content in response to user action.

LECTURE # 3
* with react , we no longer worry about querying and updating elements,instead we
describe a web page using small reusable components and react will take care of
efficiently creating and updating dom elements. Component help us write reusable ,
modular and better organized code.

* a react application is a tree of component with the app being the root bringing
everything together.

*********** Vite : : Vite is a frontend tool that is used for building fast and
optimized web applications. It uses a modern build system and a fast development
server to provide a streamlined and efficient development experience.
Vite is a local development server written by Evan You and used by default by Vue
and for React project templates. It has support for TypeScript and JSX.

Vite is known for being faster than CRA in terms of both build time and development
server startup time. This is because Vite uses a native ES modules-based
development server, while CRA uses a Webpack-based development server .

Build time refers to the amount of time it takes to compile and process your source
code, assets, and dependencies into a final distributable version of your web
application.
Development server startup time refers to the time it takes for a local development
server to initialize and become ready to serve your web application during the
development process.

****** lesson 5 first react application ************

* to create a new app using vite


first specify the location where you want the folder to be created , cd to that
location then:

npm create [email protected]


select react
select typescript
(you do not need to do the first step ,,, while doingf the second step , when you
specify project name , a folder wuth the specified name is created and all the
operations occur in that folder,,,,,while its a good practice to specify location ,
so that all your projects will remain in that specific folder)

a folder with the specified name is created , now :


cd to that folder
npm i

now open the folder in vscode


* now to run web server
npm run dev (this will launch a web server at a particular address)

****** lecture 7

.ts for plain typescript file


.tsx for react component

hmr stands for hot module replacement

* with JSX , we can easily define the user interface for our application with html
and JS. It allows us to create dynamic content.

******* lecture 8
How react works?

***** ListGroup.tsx ******

// import {Fragment} from "react";


function ListGroup() {
// class is a reserved keyword in JS and TypeScript
return (
//<h1></h1> a component cannot return more than one element ,,, this tag will
be converted to => React.createElement('h1') ,,, the same thing will happen for ul
tag ,,, one solution is that we can wrap this whole thing (h1 and ul tags) into div
, but in this way we are adding one extra element in the DOM unnecessarily ,,,, a
better way is to use fragment , in this way we are not adding an unnecessary
element in the dom ,,,,, one way is that we dont have to import fragment component
from react , instead we can simply use empty brackets , we are telling react to use
fragment to wrap all its children
<>
<h1>List Group</h1>
<ul className="list-group">
<li className="list-group-item">An item</li>
<li className="list-group-item">A second item</li>
<li className="list-group-item">A third item</li>
<li className="list-group-item">A fourth item</li>
<li className="list-group-item">And a fifth one</li>
</ul>
</>
);
}

export default ListGroup;

******** before lecture 16 ::: ListGroup.tsx *********


// import {Fragment} from "react";
// import { MouseEvent } from "react";

import { useState } from "react";

function ListGroup() {
let items = ["New York", "San Francisco", "London", "Paris"];

//Hook is a function that allows us to tap into the built in features in


react ,,, this is called state hook , using state hook we will tell react that this
component can have data or state that will change over time
// const arr = useState(-1); =>useState(-1)returns an array , now destructure it
//arr[0] => variable
//arr[1] => updater function
// each component have its own state ,, check it by adding <ListGroup/> component
in App.tsx ,, they will be independent of eachother
const [selectedIndex, setSelectedIndex] = useState(-1);

// items =[];
// in JSX , we donot have for loop and if statement
// in JS, arrays have a method called map for mapping or for converting each item
to an item of different type.
//converting each item to an li element
// in JSX , we use curly braces to render data dynamically
// this expression is not allowed in the middle of a JSX markup , in JSX we can
only use html elements and other react components , to render data dynamically ,
wrap the expression in curly braces ,,,, with braces , we can render anything
dynamically
// each list item should have a key propt that uniquely identifies that item ,
react needs this to keep track of our items , so later when we add or remove items
dynamically , react knows what part of the page should be updated
// const message = items.length === 0 ? <p>No items found</p> : null;

// const getMessage = () => {


// return items.length === 0 ? <p>No items found</p> : null;
// }

//<li className="list-group-item" key={item} onClick={() => console.log(item,


index)}>

// type annotation in typescript : with type annotation we can explicitly specify


the type of variables , parameters and so on
// const handleCLick = (event : MouseEvent) => console.log(event);

return (
<>
<h1>List Group</h1>
{items.length === 0 && <p>No items found</p>}
<ul className="list-group">
{items.map((item, index) => (
<li
className={
selectedIndex === index
? "list-group-item active"
: "list-group-item"
}
key={item}
onClick={() => {
setSelectedIndex(index);
}}
>
{item}
</li>
))}
</ul>
</>
);
}

export default ListGroup;

********* lesson 17 + 18 *******passing data via props *******

***App.tsx

import ListGroup from "./components/ListGroup";

function App() {
let items = ["New York", "San Francisco", "London", "Paris"];
const handleSelectItem = (item: string) => {
console.log(item);
};
//always close the react component <ListGroup></ListGroup>(self closing
component is used instead of this)
return (
<div>
<ListGroup
items={items}
heading="Cities"
onSelectItem={handleSelectItem}
/>
</div>
);
}

export default App;


// pass them as an input to the component , just like you pass arguments to the
function
// props => input passed to a component

********ListGroup.tsx

import { useState } from "react";

// can write anyname here , inplace of Props


interface Props {
items: string[];
heading: string;
//function that has a property of type string and returns void
onSelectItem: (item: string) => void;

// we are using type annotation to specify the types of different properties


}

// give parameter called props of type Props ( can give any name) --> function
ListGroup(props: Props)
// we donot have items variable(array) anymore but we have to prefix it with props.
example::: props.items.length and props.items.map but it is repetitive
// better solution is to destructure the parameter here --> function
ListGroup(props: Props)
function ListGroup({ items, heading, onSelectItem }: Props) {
const [selectedIndex, setSelectedIndex] = useState(-1);
// heading=""; => cannot change props ,, nothing will happen but it is an anti-
pattern in react

return (
<>
<h1>{heading}</h1>
{items.length === 0 && <p>No items found</p>}
<ul className="list-group">
{items.map((item, index) => (
<li
className={
selectedIndex === index
? "list-group-item active"
: "list-group-item"
}
key={item}
onClick={() => {
setSelectedIndex(index);
onSelectItem(item);
}}
>
{item}
</li>
))}
</ul>
</>
);
}

export default ListGroup;


//propts are the input to our component
// using props , we can pass data to our components
// pass an object with 2 properties
// {items : [] , heading : string }
// using an interface , we can define the shape and name of the object
// we need to notify the consumer/parent of this component that an item is selected
... in this case , parent is the app component

******** Lesson 19 *********


Difference between state and props

******** Lesson 20 *********


passing children

***** App.tsx for Alert.tsx *****

import Alert from "./components/Alert";

function App() {
return (
<div className="alert alert-warning">
<Alert>Hello <i>World</i>
</Alert>
</div>
);
}

export default App;


// <Alert text="Hello Fatima!" /> => this way of passing text to a component is
ugly , what if we want to pass html content
// it is nicer to pass text as a child to this component => <Alert>Hello
<i>World</i> </Alert>

****** lecture # 22 *******


**** App.tsx

import Button from "./components/Button";

function App() {
return (
<div>
<Button color="dark" onClick={()=> console.log("CLICKED")}>My button</Button>
</div>
);
}

export default App;


// <Alert text="Hello Fatima!" /> => this way of passing text to a component is
ugly , what if we want to pass html content
// it is nicer to pass text as a child to this component => <Alert>Hello
<i>World</i> </Alert>

***** Button.tsx
interface Props {
children: string;
color?: "primary" | "secondary" | "danger" | "dark"; // we can only set this
property to the value of primary ,,, add union operator to support other
colors ,,, we can set this property to only one of these values, nothing
else ,,, ? => optional
onClick: () => void;
}

export const Button = ({ children, color = "primary", onClick }: Props) => {


return (
<button type="button" className={"btn btn-" + color} onClick={onClick}>
{children}
</button>
);
};
export default Button;

****** Lecture 27 : css in js ********


App.tsx

import ListGroup from "./components/ListGroup"; // reference to the ListGroup


folder without the ListGroup file ,, by deafault if we donot supply a file and here
we are referencing a folder, the compiler will look for a file called index

function App() {
let items = ["New York", "San Francisco", "London", "Paris"];
const handleSelectItem = (item: string) => {
console.log(item);
};
//always close the react component <ListGroup></ListGroup>(self closing
component is used instead of this)
return (
<div>
<ListGroup
items={items}
heading="Cities"
onSelectItem={handleSelectItem}
/>
</div>
);
}

export default App;


// pass them as an input to the component , just like you pass arguments to the
function
// props => input passed to a component
ListGroup.tsx

import { useState } from "react";


// import "./ListGroup.css";
import styled from "styled-components";
// with styled components we no longer use class name , instead we create a react
component that has all the styles we want right here
// styled. __ => will give you atleast all the html elements
// the return value of this is going to be a react component that has all the
styles applied to it ,,, so we can store it in a component called List ,,, so
instead of using regular html element , we can use react component
const List = styled.ul`
list-style: none;
padding: 0;
color: pink;
`;
interface ListItemProps {
active: boolean;
}
const ListItem = styled.li<ListItemProps>`
padding: 5px 0;
background-color: ${(props) => (props.active ? "blue" : "brown")};// template
literal in TS or JS
`;

interface Props {
items: string[];
heading: string;

onSelectItem: (item: string) => void;


}

function ListGroup({ items, heading, onSelectItem }: Props) {


const [selectedIndex, setSelectedIndex] = useState(0);

return (
<>
<h1>{heading}</h1>
{items.length === 0 && <p>No items found</p>}
<List className="list-group">
{items.map((item, index) => (
<ListItem
active={index === selectedIndex}
key={item}
onClick={() => {
setSelectedIndex(index);
onSelectItem(item);
}}
>
{item}
</ListItem>
))}
</List>
</>
);
}
export default ListGroup;
// in this way , JSX markup only represents the structure and no styling ,,, so all
the styling is done on the top in a single place ,,, everything we need to built a
listGroup component is included in this file ....... with this approach , it is
easier to style a component based on its props or state

******* Lesson 28: Separation of concerns ********


// does not mean on whether to create separate files or put everything in a single
file, instead it emphasizes that different sections or modules in a program should
handle specific functionalities and all the complexity around the piece of
functionality should be hidden behind a well-defined interface
//props are like buttons on the remote control, they represent the interface of our
list group component ,, the consumer of this component does not care what is behind
this interface , doesnot care whether we put everything in one file or multiple
files

******** Lesson 29: Inline styles *******


// style propt that we can set to object , inside the braces we add the object, (we
can also create this object outside the markup) ,,, now in this object we can set
various css attribute ,,,, all the css attributes are available here but they are
named differently
<ul style={{backgroundColor: "yellow", color: "orange"}}>
</ul>

******** Lesson 30: Popular UI libraries ********

Bootstrap: It gives us a buch of useful components


material UI: Is an open source react component library that impelments google
material designs. Its a design language used in google products
Tailwind CSS: utility CSS library. Instead of giving us a full fleged components ,
it give us really small utility classes that we can use to style our components

<ul className="list-none p-0">

SAME EFFECT AS::::


.list-group{
list-style: none;
padding: 0;
}

with tailwind , we never had to write css by hand anymore instead we use these
utility classes

Daisy UI : gives us full fledged component with cleaner markup , instead of utility
classes , we can use componennts (similar to bootstrap)

<div class="alert alert-success">

Chakra UI: is similat to material UI ,,,, it is a react component library that is


built ontop of tailwind ,, not as comprehensive as material UI but it has all the
necessary UI components

******* Lesson 31 : adding icons *******

popular library : react icons


npm i [email protected]

react icons is essentially a package of different icon libraries( icons across


different libraries)
(search on google : react icons)
if you see icon names , all of them have a prefix
FOR EXAMPLE: BsFillCalendar
an icon that has a prefix of Bs that is short for bootsrap

*** App.tsx

import { BsAlarmFill } from "react-icons/bs";


// BsAlarmFill: this is essentially a react component , we import it from react-
icons/bs ,,, /bs becz the component starts from bs so go to bs library

function App(){

return(
<div>

< BsAlarmFill color="red" size="40" />


</div>
)
}
export default App;

// < BsAlarmFill /> :::: like a regular react component

****** Lecture 32 ::: Exercise: Using CSS Modules *******

*** MY approach
* app.tsx

import Button from "./components/Button";

function App(){

return(
<div>
<Button onClick={()=>console.log("hello here")}>myButton</Button>
</div>
)
}
export default App;

* Button .tsx
import "./Button.css";
interface Props {
children: String;
onClick: () => void;
}

const Button = ({ children, onClick}: Props) => {


return <button className="bg col" onClick={onClick}>{children}</button>;
};
export default Button;

* Button.css
.bg{
background-color: aqua;
font-style: italic;
}
.col{
color: blue;
}

******using css modules


* app.tsx
import Button from "./components/Button/Button"

function App() {
return (
<div>
<Button color="primary" onClick={()=> console.log("CLICKED")}>My
button</Button>
</div>
);
}

export default App;


* button.tsx
import styles from "./Button.module.css"

interface Props {
children: string;
color?: "primary" | "secondary" | "danger" | "dark"; // we can only set this
property to the value of primary ,,, add union operator to support other
colors ,,, we can set this property to only one of these values, nothing
else ,,, ? => optional
onClick: () => void;
}

export const Button = ({ children, color = "primary", onClick }: Props) => {


return (
<button type="button" className={[styles.btnothers, styles["btn" +
color]].join(" ")} onClick={onClick}>
{children}
</button>
);
};
export default Button;

* button.module.css
.btnprimary{
background-color: blue;
color: white;
}
.btnothers{
padding: 5px 9px;
border: none;
border-radius: 3px;
}
********** Lesson 33: Building a like component *********
*App.tsx:

import Like from "./components/Like";

function App() {
return (
<div>
<Like onClick={() => console.log("clicked")} />
</div>
);
}

export default App;


//App => interface ,,, in terms of interface , this component should have a
props

*Like.tsx

import { FaRegHeart, FaHeart } from "react-icons/fa";


// import { FaHeart } from "react-icons/fa";
import { useState } from "react";

interface Props {
onClick: () => void;
}

const Like = ({ onClick }: Props) => {


const [selectedIcon, setSelectedIcon] = useState(false);
const toggle = () => {
setSelectedIcon(!selectedIcon);
onClick();
};

if (selectedIcon) return <FaHeart color="red" size={30} onClick={toggle} />;


return <FaRegHeart size={30} onClick={toggle} />;
};
export default Like;

// In this application , we have initialized status to true , but in a real world


this has to be done dynamically depending on the content we are presenting to the
user . If we are presenting a list of tweets, the user has liked only some of those
tweets, not all of them ,,we should not har code values like false or true here ,
instead we should have a props for initializing selectedIcon variable

******** Lesson 34 : Managing component state ,,,, state management ******


CRUCIAL CONCEPT
How component state is stored and updated

******** Lesson 35 : Understanding the state hook ********


STATE HOOK :
**** using state hook , we can add state to a component
* react update state asynchronously, meaning not immediately ,,, this is done for
performance reason. Because as a part of handling events , we could set multiple
state variable. For Example: setName("Noor") , now every time we call the set
function, react rerenders the component will end up with two many rerenders which
is unnnecessary . So for performance reasons, react takes all of these updates
batches them and apply it at a later time( after event handler function finishes
execution). So at that point, react applies all the updates at once and then it
will rerender our component with the updated state.

EXAMPLE HERE::::

import { useState } from "react";

function App() {
const [isVisible, setVisibility] = useState(false);
const handleClick = () => {
setVisibility(true);
//setName("noor");
console.log(isVisible);// it will display the old value of visible
};// handleClick function finishes executing here
return (
<div>
<button onClick={handleClick}>Show</button>
</div>
);
}

export default App;

Here's a more accurate breakdown of the process:

State updates: When you call a state update function, React schedules the update,
but the actual re-rendering doesn't happen immediately.

Reconciliation: React reconciles the changes by comparing the previous virtual DOM
with the new one generated after the state update. It identifies what needs to be
changed in the actual DOM.

Updating the DOM: React updates the actual DOM efficiently by applying only the
necessary changes identified during the reconciliation phase.

* State is stored outside of components

import { useState } from "react";

function App() {
const [isVisible, setVisibility] = useState(false);
let count = 0;
const handleClick = () => {
setVisibility(true);
count ++;
//setName("noor");
console.log(isVisible);// it will display the old value of visible
};// handleClick function finishes executing here
return (
<div>
<button onClick={handleClick}>Show</button>
</div>
);
}

export default App;

// variables declared inside functions are scoped to that fucntion . When the App
function finishes execution(which happens on every rerender), local variables are
removed from the memory ,,, that means the next time react rerenders the component
is going to call App function again and count will bne initialized to 0 again. So
this(count ++;) update will be lost . This is the reason that we use state hook to
store the state outside of this component ,,, so react will store all the state
variables for this component and it will automatically remove those variabls when
this component is no longer visible on the screen

// In essence, the comment underscores the importance of using React state hooks to
maintain and manage state across re-renders, as opposed to relying on local
variables that get reset on each render. This ensures that the state persists and
remains consistent even as the component refreshes or updates

// In essence, the automatic cleanup of state variables managed by useState in


React ensures that resources associated with those variables are properly released
when the component is unmounted. This helps in preventing memory leaks, optimizing
memory usage, and maintaining the performance and stability of React
applications ,,, Memory optimization refers to the process of improving the
efficiency and utilization of computer memory resources.
// state variables are actualy created and stored outside the component in
memoory ,, react keeps the state in memory as long as the component is visible on
the screen
* use hooks at the top level of your component

import { useState } from "react";

// these are just local identifiers to this fn, react is not aware of these names,
when we use state hooks , we tell react that I want to store two boolean values,,,,
react will store these values inside an array [false, true] ,,, next time react
rerenders our component is goung to look at this array is going to grab first
element and store its value inside isVisible.... react realize under order of these
elemets so it can map these values properly to the local varibles . we cannot use
hooks insides if statement , for loops and nested fns becz these constructs can
impact the order in which the state hook is called, so to ensure a consistent order
, we can only call hooks at the top level of our component
function App() {
const [isVisible, setVisibility] = useState(false);
const[isApproved, setApproved ]= useState(true);

let count = 0;
const handleClick = () => {
setVisibility(true);
count ++;
//setName("noor");
console.log(isVisible);// it will display the old value of visible
};// handleClick function finishes executing here
return (
<div>
<button onClick={handleClick}>Show</button>
</div>
);
}

export default App;

******* Lesson 36: Choosing the state structure *******


ONE:
import { useState } from "react";

function App() {
const[firstName, setFirstName] = useState('b');
const[lastName, setLastName] = useState('n');
const fullName = firstName + ' ' + lastName; // donot use state hook to declare
fullName , this is redundant

// sometimes state variables are related , so better to group in objects


return (
<div>
{/* {firstName} {lastName} */}
{fullName}
</div>
);
}

export default App;

TWO:
import { useState } from "react";

function App() {
const [person, setPerson] = useState({
firstName: " ",
lastName: " "
// when using objects donot go for deeply nested structure like this (updating
an object with this structure is difficult)
contact: {
address: {
street: " "
}
}
});
const [isLoading, setLoading] = useState(false);
return <div></div>;
}

export default App;

in short;
avaoid redundand state variable like full name
group related variables inside an object
avoid deeply nested structures becz they are hard to update ,, so prefer to use a
flat structure

****** Lecture 37: Keeping components pure *******

pure function : give the same input , always returns the same result
square program example

react expects every component we create to be a pure function , so if we give the


same props(input) to the component, always return the same JSX and this is for
performance reason ,,, so if the inputs of a component have not change , react
can skip re-rendering that component

** to keep componnets pure , keep changes out of the render phase


**** ONE

import Message from "./components/Message";


function App(){
return(
<div>
<Message/>
<Message/>
<Message/>
</div>
)
}
export default App

Message.tsx

let count = 0;

const Message = () => {


return <div>Message{count}</div>;
};
export default Message;

**** TWO

import Message from "./components/Message";


function App(){
return(
<div>
<Message/>
<Message/>
<Message/>
</div>
)
}
export default App
let count = 0;

const Message = () => {


count++; // update count variable as part of rendering this component
return <div>Message{count}</div>;
};
export default Message;
// this component is impure

*** THREE
import Message from "./components/Message";
function App(){
return(
<div>
<Message/>
<Message/>
<Message/>
</div>
)
}
export default App

const Message = () => {


let count = 0; // count variable is declared as part of rendering
count++; // update count variable as part of rendering this component
return <div>Message{count}</div>;
};
export default Message;

// to keep components pure , we should keep changes out of the render phase, we
should not change any object that exists before rendering (count)
// but its totally fine to update object that we create as part of rendering

****** LEcture 38 ::: Understanding the strict mode *****


ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
<React.StrictMode>
<App />
</React.StrictMode>
);
"We are passing this component tree to react DOM for rendering insidethe browser.
OUR app component is wrapped inside the StrictMode component in react . this is one
of the built in components in react that does not have the visual representation.
Its only there to catch potential problems , one of which is impure components.
When strict mode in enable , react renders each component twice ... that is why we
get message 2,4,6 instead of 1,2,3

<Message> Message 1
Message 2

.. ... 3
... 4

.. ..5
.. 6

and takes the result of the 2 render


the 1 render is used for detecting and reporting primarily issues with our code
while the 2 render is used the update the UI.
import Message from "./components/Message";
function App(){
return(
<div>
<Message/>

</div>
)
}
export default App

Message.tsx
let count = 0;

const Message = () => {


console.log("message called ", count);
count++; // update count variable as part of rendering this component
return <div>Message{count}</div>;
};
export default Message;

** check in the console,the second message is coming out from the strict mode
starting from react 18 , strict mode is turned on by default , as aresult react
renders each component twice,,,with this we can find impure component like we see
message component here ,, so we dont see the result we expect

** strict mode only behaves this way in dev mode ,,, so when we built application
for production , the strict mode checks are not included and our components will
render only once

********** LEsson 39 : Updating objects *********


import {useState} from "react"
function App(){
// group related state variables inside an object
// drink object with 2 properties
const[drink, setDrink]= useState({
title: "Americano",
price: 5,
});

const handleClick = () => {


// it will not work
// drink.price=6;
// setDrink(drink); pass the drink object

// to tell react about state updates , we have to give react a brand new object
,, so instead of updating an existing state object , we shoulg give react a brand
new state object ,,, so just like props, we should treat state objects as immutable
or read only
// const newDrink = {
// title: drink.title,
// price:6
// }
// setDrink(newDrink);

// if there are many properties , we will not copy all of them , we can use the
spread operator in JS (...) to spread the existing drink object ,,, copy all the
existing properties of drink object to newdrink object
// const newDrink = {
// ...drink,
// price:6
// }
// setDrink(newDrink);

// above can be wriiten as:


setDrink({...drink, price:6})

}
return(
<div>
{drink.price}
<button onClick={handleClick}> Click Me </button>

</div>
)
}
export default App
// when dealing with objects and arrays you should treat them as immutable or read
only

********** Lesson 40 : Updating nesting objects **********


import { useState } from "react";
function App() {

const [customer, setCustomer] = useState({


name: "John",
address: {
city: "San francisco",
zipCode: 94111,
},
});
// the spread operator in JS is shallow which means when we use it to copy
customer object, is going to return the existing address object in memory. So that
means if we create two customer objets this way, both of them are going to
reference the same address object in memory, this is not what we want. When
updating states in react applications , we should make sure that our new state
object is completely independent of existing state object , they donot share
anything,, to solve this problem further set the address object
const handleClick = () => {
setCustomer({
...customer,
address: { ...customer.address, zipCode: 87069 },// completely independent of
that address object
// ...customer.address, copy all the properties city and zip code and then
changed the zipcode
// we should avoid deeply nesting structures, becz deeper the structure gets
the more complicated our upodate logic will be
});
};
return (
<div>
<button onClick={handleClick}> Click Me </button>
</div>
);
}
export default App;
// when dealing with objects and arrays you should treat them as immutable or read
only

*********** Read more and less exercise **************


import Read from "./components/Read"
function App() {

return (
<div>
<Read>API stands for Application Programming Interface. In the context of
APIs, the word Application refers to any software with a distinct function.
Interface can be thought of as a contract of service between two applications. This
contract defines how the two communicate with each other using requests and
responses. Their API documentation contains information on how developers are to
structure those requests and responses. API architecture is usually explained in
terms of client and server. The application sending the request is called the
client, and the application sending the response is called the server. So in the
weather example, the bureau’s weather database is the server, and the mobile app is
the client. </Read>

</div>
);
}
export default App;

import { useState } from "react";

interface Props {
children: string;
}
const Read = ({ children }: Props) => {
const [selectedState, setSelectedState] = useState(false);
const toggle = ()=> {
setSelectedState(!selectedState)
}
if (children.length > 100 && !selectedState)
return <div>{children.slice(0, 30)} <span style={{color: "blue"}}
onClick={toggle}> ...... Read more</span></div>;
else if(children.length > 100 && selectedState)
return <div>{children} <span style={{color: "blue"}} onClick={toggle}> ...... Read
less </span></div>
else
return <div>{children}</div>;
};
export default Read;

*************** for practice ***************


import React, { useState } from 'react';

const InputComponent = () => {


const [inputValue, setInputValue] = useState('Hello');

const handleInputChange = (e) => {


setInputValue(e.target.value);
};

return (
<div>
<label htmlFor="myInput">Type something:</label>
<input
type="text"
id="myInput"
value={inputValue} // The value attribute is set to the state variable
inputValue
onChange={handleInputChange} // onChange event handler to update the state
/>
<p>You typed: {inputValue}</p>
</div>
);
};

export default InputComponent;

****************** Create a simple To-Do List application using React. The app
should have the following features:

Add Task: Allow users to add new tasks to the list.


Display Tasks: Display the list of tasks.
Mark as Completed: Users should be able to mark tasks as completed.
Delete Task: Allow users to delete a task.
Task Count: Display the total number of tasks and the number of completed
tasks.***************

*********** Lesson 41 : updating arrays ***********

import { useState } from "react";

function App() {
const [tags, setTags] = useState(["happy", "cheerful"]);

const handleClick = () => {


// add an object
// donot do tags.push() becz this modifies the original array
// this will copy all the items in the original array into the new array and
then add a new object
// setTags([...tags, "exciting"]);

// remove an object
// all arrays have a filter method
// we want to get all the tags except the "happy" tag
// setTags(tags.filter((tag) => tag !== "happy"));

// update an object
// setTags(tags.map((tag) => (tag === "happy" ? "happiness" : tag)));
};

return <div>
<ul>
{tags.map((tags) => <li>{tags}</li> )}
</ul>
<button onClick={handleClick}>click me</button>
</div>;
}
export default App;
// we should not change array , instead we should give react a brand new array

**************** Lesson 42 : updating array of objects ****************

import { useState } from "react";

function App() {
const [bugs, setBugs] = useState([
{ id: 1, title: "Bug 1", fixed: false },
{ id: 2, title: "Bug 2", fixed: false },
]);

const handleClick = () => {


setBugs(bugs.map((bug) => bug.id === 1 ? {...bug, fixed: true} : bug))
};
return <div></div>;
}

export default App;


// we have an array with two bug objects b1 and b2 , as a part of handling events
we create a new array with 2 objects,,, we have a brand new object b1* and the same
b2 object as we have in the original array (with this , we tell react that the
first object in this array is updated and react will do the necessary work to
update the dom to match the component state)
// we donot need to make a brand new copy of bug object in this array,,, only the
object that supposed to be modified

******** Lesson 43 : simplifying update logic with immer ********

import { useState } from "react";


import produce from "immer";
// use immer library to simplify update logic

function App() {
const [bugs, setBugs] = useState([
{ id: 1, title: "Bug 1", fixed: false },
{ id: 2, title: "Bug 2", fixed: false },
]);

const handleClick = () => {


// setBugs(bugs.map((bug) => bug.id === 1 ? {...bug, fixed: true} : bug))

// by convention , we call the parameter of the function as draft


// draft is a proxy object that records the changes we are going to apply to
the bugs array
// imagine draft is the copy of the bugs array so we are free to mutate jsut
like a regular JS object so we donot need to map anything , we donot need to create
copies
// draft is the array of bugs
setBugs(
produce((draft) => {
// you can go into this object and make any changes , behind the scenes
immer will keep track of those changes then it will create a copy of those bugs
array with our changes applied(Behind the scenes, immer keeps track of these
modifications and creates a new immutable state based on the changes applied to the
draft)
const bug = draft.find((bug) => bug.id === 1);
if (bug) bug.fixed = true; // so we are mutating this object just like a
regular JS object
})
);
};
return (
<div>
{bugs.map((bug) => (
<p key={bug.id}>
{bug.title} {bug.fixed ? "Fixed" : "New"}{" "}
</p>
))}
<button onClick={handleClick}>Click ME</button>
</div>
);
}

export default App;

********** Lesson 44 : Sharing state between components **********


*** App.tsx

import { useState } from "react";


import NavBar from "./components/NavBar";
import Cart from "./components/cart";

function App() {
// in real , we have product object , where each product has id, title, price and
so on
const [cartItems, setCartItems] = useState(["Product1", "Product2"]);

return <div>
<NavBar cartItemsCount={cartItems.length}/>
<Cart cartItems={cartItems} onClear= {() =>setCartItems([])}/>

</div>;
}

export default App;


// app component and below that we have navbar and cart component ,, we have used
the state hook in the cart component to store the list of shopping items
// to share the state with the navbar component, we have to lift the state up to
the closest parent(app),,, now that we have state in the app component, we can
share it with its children
// all the updates should be done in the app component becz we have mantained the
state in the app component

*** NavBar.tsx
import React from "react";

interface Props {
// cartItems: string[] access to all the shopping cart items
cartItemsCount: number
}
const NavBar = ({cartItemsCount}: Props) => {
return <div>NavBar: {cartItemsCount}</div>;
};
export default NavBar;

*** Cart.tsx

import React from "react";

interface Props {
cartItems: string[];
onClear: () => void;
}

const Cart = ({ cartItems, onClear}: Props) => {


return (
<>
<div>Cart</div>
<ul>
{cartItems.map((item) => (
<li key={item}>{item}</li>
))}
</ul>
<button onClick={onClear}>Clear</button>
</>
);
};
export default Cart;
// we should treat the props as immutable or read only
// so we are not going to modify this object (cartItems) here(Cart.tsx) ,,, instead
we need to modify the app component that the user clicks on the clear button so all
the changes to the state should be done inside the App component
// as a rule of thumb, remember the component that hold the state is the one
responsible for updating it

******* Lesson 45 : Exercise Updating State *********

**** Exercise 1:
import { useState } from "react";
function App() {
const [game, setGame] = useState({
id: 1,
player: {
name: "John",
},
});

const handleClick = () => {


setGame({
...game,//spread operator is shallow, its not going to copy the player object
, its going to use the same player object referenced in the above game object
// so we need to create a brand new player object
player: {...game.player , name: "Hero"}
})
};
return <div></div>;
}

export default App;

******** Exercise: 2 *********

import { useState } from "react";

function App() {
const [pizza, setPizza] = useState({
name: "Spicy pepproni",
toppings: ["Mushroom", "Olives"],
});

const handleClick = () => {


//outside the react world : pizza.toppings.push("Cheese");
setPizza({ ...pizza, toppings: [...pizza.toppings, "Cheese"] });
};
return (
<div>
<button onClick={handleClick}>Click ME</button>
</div>
);
}

export default App;

******** Exercise : 3 ********

import { useState } from "react";

function App() {
const [cart, setCart] = useState({
dicount: 0.1,
items: [
{ id: 1, title: "Product 1", quantity: 1 },
{ id: 2, title: "Product 2", quantity: 1 },
],
});
const handleClick = () => {
setCart({
...cart,
items: cart.items.map((item) =>
item.id === 1 ? { ...item, quantity: item.quantity + 1 } : item
),
});
};
return (
<div>
<button onClick={handleClick}>Click me</button>
</div>
);
}

export default App;

****** Lesson : 46 Building an expnadable Text Component *******

**** my method
App.ysx
import ExpandableText from "./components/ExpandableText";

function App() {
return (
<div>
<ExpandableText maxChars={10}>
Introduction The Passport Management System is a software application
designed to efficiently manage passport-related information for
individuals. It serves as a comprehensive database system that
facilitates the storage, retrieval, modification, and deletion of
passport records. The system is developed using C++ programming language
and is intended to simplify the administrative process associated with
passport issuance and management.s in handling sensitive information. By
developing an automated solution, we aimed to streamline the process of
passport management, reduce human errors, enhance data security, and
improve overall efficiency in passport-related operations.Searching
Passport Records.
</ExpandableText>
</div>
);
}

export default App;

ExpandableText.tsx

import React, { useState } from "react";

interface Props {
children: string;
maxChars: number;
}

const ExpandableText = ({ children, maxChars }: Props) => {


const [status, setStatus] = useState(false);

const handleClick = () => {


setStatus(true);
};

if (!status)
return (
<div>
<>
<p>{children.slice(0, maxChars)} ....</p>
<button onClick={handleClick}>Read more</button>
</>
</div>
);
else return;
};

export default ExpandableText;


// half logic is missing

****** mosh method

App.tsx

import ExpandableText from "./components/ExpandableText";

function App() {
return (
<div>
<ExpandableText maxChars={10}>
Introduction The Passport Management System is a software application
designed to efficiently manage passport-related information for
individuals. It serves as a comprehensive database system that
facilitates the storage, retrieval, modification, and deletion of
passport records. The system is developed using C++ programming language
and is intended to simplify the administrative process associated with
passport issuance and management.s in handling sensitive information. By
developing an automated solution, we aimed to streamline the process of
passport management, reduce human errors, enhance data security, and
improve overall efficiency in passport-related operations.Searching
Passport Records.
</ExpandableText>
</div>
);
}

export default App;

*** ExpandableText.tsx
import React, { useState } from "react";

interface Props {
children: string;
maxChars?: number;
}
const ExpandableText = ({ children, maxChars = 100 }: Props) => {
const [isExpanded, setExpanded] = useState(false);

if (children.length <= maxChars) return <p>{children}</p>;


const text = isExpanded ? children : children.substring(0, maxChars);

return (
<p>
{text}...
<button onClick={() => setExpanded(!isExpanded)}>
{isExpanded ? "Less" : "More"}
</button>
</p>
);
};

export default ExpandableText;

// we should not use the state hook for any values that can be computed like the
full name of a person or this text here , we use the state hook only for storing
values that might change over time and the change require re rendering our
component . And in this case our component should be re rendered when the value of
the variable isExpanded changes

*********** Lesson 48 + 49+ 50 : Building a form + Handling Form Submission +


Accessing input fields *********
import React, { FormEvent, useRef } from "react";

const Form = () => {


// useRef(null) => this returns a reference object
const nameRef = useRef<HTMLInputElement>(null); // to tell the ts compiler that
we are referencing an html input element
const ageRef = useRef<HTMLInputElement>(null);
const person = { name: " ", age: 0 };
const handleSubmit = (event: FormEvent) => {
event.preventDefault(); // prevent the form from being posted to the server
if (nameRef.current !== null) person.name = nameRef.current.value;

if (ageRef.current !== null) person.age = parseInt(ageRef.current.value);


// we have defined age property as a number and the value property returns a
string => error
// solution => parseInt to convert a string to a number

console.log(person);
};
return (
<form onSubmit={handleSubmit}>
<div className="mb-3">
<label htmlFor="name" className="form-label">
Name
</label>
<input ref={nameRef} id="name" type="text" className="form-control" />
</div>

<div className="mb-3">
<label htmlFor="age" className="form-label">
Age
</label>
<input ref={ageRef} id="age" type="number" className="form-control" />
</div>
<button className="btn btn-primary" type="submit">
Submit
</button>
</form>
);
};

export default Form;


// set htmlFor attribute to the id of this input so when the user clicks on the
label , the input automatically gets focused

// <form onSubmit={() => console.log("Submitted")}> if we submit the form , the


msg on the console disappears because by default when we submit an html form that
form is posted to the server so we get a full page reload
// to prevent the above default behaviour => prevent the form from being posted
to the server

// in real world applications , when we submit a form, we need to call the server
to save the data

//In React, the onSubmit event handler is a property that you can attach to a
<form> element. It specifies the action to be taken when the form is submitted.

// In react , we have another built in hook called useRef that we can use to
reference a dom element ..... how can we use useRef to reference an input field
and reads its value when the form is submitted ..... using the ref hook we can
reference any type of element in the dom, not necessarily input fields but buttons,
heading and so on

// console.log(nameRef.current); // this returns the DOM element we are


referencing
// using the ref hook we can reference any type of element in the dom, not
necessarily input fields but buttons, heading and so on , so thats why we encounter
the error and then to resolve it we tell the ts compiler the we are referencing an
html input element

// when submitting form, we need to send an to the server to be saved

// why to initialize every ref object with null : the current property of ref
object references a dom node, now the initial value that we pass here (null) will
be used to set the current property ... when we create a ref object, we donot have
access to the dom node becz the dom is created after the react renders the
component, so we really have no initial value to provide here(null) ... when react
renders the component and creates the dom , it will set the current property to the
dom node and it will set it back to null when the node is removed from the
screen ... so the current property should be either null or referencing an existing
DOM ... if we donot provide an initial value here(null), the current property will
be undefined

********** Lecture 51 : Controlled components *********


import React, { FormEvent, useRef, useState } from "react";

const Form = () => {


const [person, setPerson] = useState({
name: "",
age: 0
});
const handleSubmit = (event: FormEvent) => {
event.preventDefault(); // prevent the form from being posted to the server
console.log(person);
};
return (
<form onSubmit={handleSubmit}>
<div className="mb-3">
<label htmlFor="name" className="form-label">
Name
</label>
<input
onChange={(event) =>
setPerson({ ...person, name: event.target.value })
}
value={person.name}
id="name"
type="text"
className="form-control"
/>
</div>

<div className="mb-3">
<label htmlFor="age" className="form-label">
Age
</label>
<input
onChange={(event) =>
setPerson({ ...person, age: parseInt(event.target.value) })
}
value={person.age}
id="age"
type="number"
className="form-control"
/>
</div>
<button className="btn btn-primary" type="submit">
Submit
</button>
</form>
);
};

export default Form;


// onChange: triggers eveery time the user types a keystroke
// from event.target we can access this input field

//with this approach(useState), when everytime the user type or remove a character
becz we update the state variable , react rerenders the component

// PROBLEM:
// inputs have value property for maintaining there own state
// in this implememntation , we also have a state variable person so it is
posssible that this source gets out of sync ... to solve this problem , we should
make react a single source of truth .... value = {person.name} this input field
always relies on the value in the state variable ,,, we have a single source for
storing the name of the person ... now we are refering to this input field as a
controlled component becz its state is internally controlled by react, this means
the value of the input field is not managed by the dom but instead is stored and
updated in the component state
// In the provided React code, the value attribute is used in conjunction with the
<input> elements to create controlled components. Controlled components in React
are those where the value of the input fields is controlled by the component's
state. he value attribute in this context serves as the binding between the input
fields and the component state. It ensures that the input fields display the values
stored in the person state and any changes made to these fields are reflected in
the state via the onChange event handler. This approach of using controlled
components helps maintain a single source of truth for the input values within the
React component.

//PREMATURE OPTIMIZATION IS THE ROOT OF ALL EVIL

// if you observe performance issues then use ref HOOK otherwise use useState

********
Lesson 52 : managing forms with react hook form ********

import React, { FormEvent, useRef, useState } from "react";


import { useForm, FieldValues} from "react-hook-form"; // useForm : a custom hook

const Form = () => {


// const form = useForm();
// console.log(form); => to see what methods or properties or functions form
object have

// destructure this form object and grab the register function


const { register, handleSubmit } = useForm();
//console.log(register("name")); => this returns an object

// in real apps , submit the data to the server


const onSubmit = (data: FieldValues ) => console.log(data);

// submitHandler is a function that receives the data in the form


return (
<form onSubmit={handleSubmit(onSubmit)}>
<div className="mb-3">
<label htmlFor="name" className="form-label">
Name
</label>
<input
{...register("name")}
id="name"
type="text"
className="form-control"
/>
</div>

<div className="mb-3">
<label htmlFor="age" className="form-label">
Age
</label>
<input
{...register("age")} // pass a key
id="age"
type="number"
className="form-control"
/>
</div>
<button className="btn btn-primary" type="submit">
Submit
</button>
</form>
);
};

export default Form;


// react hook form : a popular library npm i [email protected]

// react hook forms use reference objects (ref) to get values from the input field
so there is no rerendering involve here .....

********* Lecture 53 *********** Applying validation *******


import { useForm, FieldValues } from "react-hook-form"; // useForm : a custom hook

interface FormData {
name: string;
age: number;
}// to define the shape of this form

const Form = () => {


// const form = useForm();
// console.log(form); => to see what methods or properties or functions form
object have

// destructure this form object and grab the register function


//formState is object
// formState: {errors} nested destructuring in JS
const {
register,
handleSubmit,
formState: { errors },
} = useForm<FormData>();
//console.log(register("name")); => this returns an object
//console.log(formState);
// console.log(formState.errors);
// in real apps , submit the data to the server
const onSubmit = (data: FieldValues) => console.log(data);

// submitHandler is a function that receives the data in the form


return (
<form onSubmit={handleSubmit(onSubmit)}>
<div className="mb-3">
<label htmlFor="name" className="form-label">
Name
</label>
<input
{...register("name", { required: true, minLength: 3 })}
// this is where the name field is registering
id="name"
type="text"
className="form-control"
/>
{errors.name?.type === "required" && (
<p className="text-danger">The name field is required.</p>
)}

{errors.name?.type === "minLength" && (


<p className="text-danger">
The name must be atleast 3 characters long.
</p>
)}
</div>

<div className="mb-3">
<label htmlFor="age" className="form-label">
Age
</label>
<input
{...register("age")} // pass a key
id="age"
type="number"
className="form-control"
/>
</div>
<button className="btn btn-primary" type="submit">
Submit
</button>
</form>
);
};

export default Form;


// react hook form : a popular library npm i [email protected]

// react hook forms use reference objects (ref) to get values from the input field
so there is no rerendering involve here .....

*********** LEsson 54 ******** scehma based validation with zod **************

********* App.tsx
import Form from "./components/Form";

function App() {
return (
<div>
<Form/>
</div>
);
}

export default App;

********** Form.tsx
import { useForm, FieldValues } from "react-hook-form"; // useForm : a custom hook
import { z } from "zod"; // to define shape and scheme for the form and all the
validation rules

import { zodResolver } from "@hookform/resolvers/zod";

const schema = z.object({


name: z.string().min(3, { message: 'Name must be atleast 3 characters long'}),
age: z.number({invalid_type_error: 'Age field is required.'}).min(18, { message:
'Age must be atleast 18'}),
});

type FormData = z.infer<typeof schema>;

const Form = () => {


const {
register,
handleSubmit,
formState: { errors },
} = useForm<FormData>({resolver : zodResolver(schema)});

const onSubmit = (data: FieldValues) => console.log(data);

return (
<form onSubmit={handleSubmit(onSubmit)}>
<div className="mb-3">
<label htmlFor="name" className="form-label">
Name
</label>
<input
{...register("name")}
// this is where the name field is registering
id="name"
type="text"
className="form-control"
/>
{errors.name && (
<p className="text-danger">{errors.name.message}</p>// zod will take care
of the error messages of generating error messages that we defined up ,,, can
cutomize the error message
)}
</div>

<div className="mb-3">
<label htmlFor="age" className="form-label">
Age
</label>
<input
// the value property of input filed always returns a string, so we need to
instruct react hookform to interpret value as a number
//empty string is not a number so we get nan
{...register("age", {valueAsNumber: true})} // pass a key
id="age"
type="number"
className="form-control"
/>
{errors.age && (
<p className="text-danger">{errors.age.message}</p>
)}
</div>
<button className="btn btn-primary" type="submit">
Submit
</button>
</form>
);
};

export default Form;

//npm i [email protected]

//integrate react hook forms with react


// this library include resolvers for various schema based validation libraries
like zod, joi etc
//npm i @hookform/[email protected]

******** Lecture 55 *** disabling the submit button (if the form is invalid )
*********

import { useForm, FieldValues } from "react-hook-form"; // useForm : a custom hook


import { z } from "zod"; // to define shape and scheme for the form and all the
validation rules

import { zodResolver } from "@hookform/resolvers/zod";

const schema = z.object({


name: z.string().min(3, { message: 'Name must be atleast 3 characters long'}),
age: z.number({invalid_type_error: 'Age field is required.'}).min(18, { message:
'Age must be atleast 18'}),
});

type FormData = z.infer<typeof schema>;

const Form = () => {


const {
register,
handleSubmit,
formState: { errors, isValid },
// errors, isValid => properties of formState
} = useForm<FormData>({resolver : zodResolver(schema)});

const onSubmit = (data: FieldValues) => console.log(data);

return (
<form onSubmit={handleSubmit(onSubmit)}>
<div className="mb-3">
<label htmlFor="name" className="form-label">
Name
</label>
<input
{...register("name")}
// this is where the name field is registering
id="name"
type="text"
className="form-control"
/>
{errors.name && (
<p className="text-danger">{errors.name.message}</p>
)}
</div>

<div className="mb-3">
<label htmlFor="age" className="form-label">
Age
</label>
<input

{...register("age", {valueAsNumber: true})} // pass a key


id="age"
type="number"
className="form-control"
/>
{errors.age && (
<p className="text-danger">{errors.age.message}</p>
)}
</div>
<button disabled={!isValid} className="btn btn-primary" type="submit">
Submit
</button>
</form>
);
};

export default Form;

********Lesson 56 ******* Project: expense tracker **********

****** lesson 63 : understanding the effect hook *********

app.tsx:::::::************

import { useEffect, useRef } from "react";

function App() {

// to get reference to this input field ,,, type of target element is


<HTMLInputElement> ,, ref is a reference object

const ref = useRef<HTMLInputElement>(null);


// after render : the function that we passed here will be called after each
render
useEffect(() => {
// side effect
if (ref.current) ref.current.focus()
})

//if(ref.current) ref.current.focus(); // this piece of code has nothing to do


with returning some JSX markup,,, with this code, we are changing the state of the
DOM ,,, this piece od code has a SIDE EFFECT , it is changing somthing outside of
this component , so our component is no longer a pure component ,,,, to make this
component pure, we can use the effect hook ,,,,,,,,,,,, move this piece of code
inside the useEffect

//just like state and ref hooks , we can call it multiple times for different
purposes

useEffect(() => {
document.title = 'My APP'
})

// when we have multiple effect hooks, react will run them in order after each
render
return (
<div>
<input ref={ref} type="text" className="form-control" />
</div>
);
}

export default App;

/* lecture 63
to keep components pure, we have to keep changes out of the render phase
side effects :
1. store data in a local storage (of the browser)
2. call the server to fetch or save data
3. manually modify the DOM
none of these situations are about rendering the component, we have nothing to do
with returning some JSX markup

props -> pure components -> JSX

this is where effect hook comes in view:


useEffect (() => {})
with effect hook , we can tell react to execute a piece of code after a component
is rendered

just like state and ref hooks ,we can call effect hooks at the top level of our
components,so we cannot calll it inside loops or if statements
just like state and ref hooks , we can call it multiple times for different
purposes

*/

************* LESSON 64 :: Effect Dependencies *************

********* APP.tsx

import { useEffect, useRef, useState } from "react";


import ProductList from "./components/ProductList";

function App() {
const [category, setCategory] = useState("");

return (
<div>
<select className="form-select" onChange={(event) =>
setCategory(event.target.value)}>
<option value=""></option>
<option value="Clothing">Clothing</option>
<option value="Household">Household</option>
</select>

<ProductList category={category} />


</div>
);
}

export default App;

**********ProductList.tsx
import React, { useEffect, useState } from "react";

//inline definition of props interface


const ProductList = ({category}: {category: string}) => {

// by default , this fn executed after each render but we want a more control over
that when this function should be executed
// if we do not specify the products variable initially , tupescript will not be
able to figure out about the type ofdata to be stored in the products variable and
will give an error even if we setProducts ,,,, so thats why , we have written
<string[]>
const [products, setProducts] = useState<string[]>([]);

// to call the server to fetch the product


useEffect(() => {
console.log("Fetching product in ", category);
setProducts(["Clothing", "Household"]);

// tell react that execute this piece of code only first time when the component is
rendered ,,, for this we have passed an array of dependencies as a 2nd
parameter(which can be props or state) ,,,, 2nd parameter is optional ,,, our
effect would be dependent on it,,,, if any of these values changes rect will run
the code/effect

// if we pass an empty array , the effect is not dependent on any values so it will
be executed only once the first time our component is rendered

// tell react that this effect should be dependent on category props,,,, so any
time the value of category changes, react should reexecute this effect
// anytime the value of any of these dependencies changes, react will rerun our
effect
}, [category]);
return <div>ProductList</div>;
};

export default ProductList;


// when the strict mode is on , react renders each component twice and this happens
only in the development mode, so it will not happen when we built our application
in the production

**********lecture : 65 Effect clean up

****** APP.tsx

import { useEffect } from "react";

const connect = () => console.log("Connecting");


const disconnect = () => console.log("Disconnecting");

function App() {
useEffect(() => {
connect();

// to provide clean up code ,,,, our clean up function should stop or undo
whatever the effect was doing
return () => disconnect();
});

return <div></div>;
}

export default App;


// what if here we are connecting to a chatserver ...... what if at some point we
need to disconnect from the chat server
// mount => attaching a react component to the DOM
// when react renders the app component for the first time , connecting is
displayed, but before react renders the component for the second time, its going to
remove the component from the screen (called unmounting),react mounts the component
on the screen and unmounts them when they are no longer needed,,, so with the
strict mode enable , before reaxt mounts the component for the second time , first
it has to unmount it ,,, that is why our clean up code is executed

**************** lesson 66: Fetching Data ******************

***** app.tsx

import axios from "axios";


import { useEffect, useState } from "react";

// we can use TS to add type safety and auto completion to our code so that we
donot access invalid property
interface User{
id: number;
name: string;

}
function App() {
// declaring a state variable for storing our users
const [users, setUsers] = useState<User[]>([]);

// to call the server


useEffect(() => {
// this method will return a promise becz calling the server will not happens
immediately
axios
.get<User[]>("https://jsonplaceholder.typicode.com/users")
.then((response) => setUsers(response.data));
}, []);

return <ul>
{users.map(user => <li key = {user.id}>{user.name}</li>)}
</ul>;
}

export default App;

/* how to fetch data from the server


fake backend : jsonplaceholder.typicode.com

axios (library) =>send http requests to teh server ,,, npm i [email protected]
PROMISE: an object that holds the eventual result or failure of an
asynchronous(an operation that might take a long time ---> long running) operation

.then(() => {}) that function gets exdcuted when our promise is resolved and
the result is ready

response is an object with many properties ,,, and one is data which is an array
of users , an array of objects with properties like id , name , username etc
in interface we will write the properties in which we are interested
console.log(response.data)
console.log(response.data[0].)
<User[]> -> specify the type of data we are going to fetch
*/

*********** LEcture 67 ************ understanding http requests **********

import axios from "axios";


import { useEffect, useState } from "react";

// we can use TS to add type safety and auto completion to our code so that we
donot access invalid property
// in response , we will get all the data ,,, all the properties other than id name
,, but here we are accessing just two of them
interface User {
id: number;
name: string;
}
function App() {
// declaring a state variable for storing our users
const [users, setUsers] = useState<User[]>([]);

// to call the server


useEffect(() => {
// this method will return a promise becz calling the server will not happens
immediately
axios
.get<User[]>("https://jsonplaceholder.typicode.com/users")
.then((response) => setUsers(response.data));
}, []);

return (
<ul>
<p>rendered</p>
{users.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}

export default App;


/*
http : protocol for transfering data over the internet(this protocol powers the
web)
when we call get method , axios sends an http request to the server

XHR -> XML HTTP Request


in http : every request and response has two sections
1) headers: where we specify the meta data
2) body: where you supply or get the data
*/

*********** LEsson 68: Handling errors *************

import axios from "axios";


import { useEffect, useState } from "react";

interface User {
id: number;
name: string;
}
function App() {
const [users, setUsers] = useState<User[]>([]);

const [error, setError] = useState("");

//declare a state variable for errors

useEffect(() => {
axios
.get<User[]>("https://jsonplaceholder.typicode.com/xusers")
.then((response) => setUsers(response.data))
.catch((err) => setError(err.message));
}, []);

return (
<>
{error && <p className="text-danger">{error}</p>}
<ul>
{users.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
</>
);
}

export default App;


// catch block will be executed if something goes wrong while fetching the data
(our network connection can drop out and may be the server will go offline)

******* Lesson 69 : Working with async and await *******


import axios, { AxiosError } from "axios";

import { useEffect, useState } from "react";

interface User {
id: number;
name: string;
}
function App() {
const [users, setUsers] = useState<User[]>([]);

const [error, setError] = useState("");

useEffect(() => {
const fetchUsers = async () => {
try{
const res = await axios.get<User[]>(
"https://jsonplaceholder.typicode.com/xusers"
);
setUsers(res.data)
}
catch(err){
setError((err as AxiosError).message)
}
};

fetchUsers();
}, []);

return (
<>
{error && <p className="text-danger">{error}</p>}
<ul>
{users.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
</>
);
}

export default App;


// get returns a promise , if this promise is resolved we get a response object ,
but if this promise is rejected we get err object

// in JS , if we had a promise , we can put the await keyword infront of the


promise to get the result(response object)

/*

- react does not allow to pass async function to the effect hook
- to solve this , we define another function inside the effect hook

- type annotation is not allowed in the catch block , thats why we have done this
(err as AxiosError)
*/
*********** Lesson 70 : Cancelling a fetch request ***********
import axios, { CanceledError } from "axios";
import { useEffect, useState } from "react";

interface User {
id: number;
name: string;
}
function App() {
const [users, setUsers] = useState<User[]>([]);

const [error, setError] = useState("");

useEffect(() => {
// this is a built in class in modern browsers that allows us to cancel or
abort asynchronous operations like fetch requests , dom manipulation or any
operation that might take a long time to complete
const controller = new AbortController();
axios
.get<User[]>("https://jsonplaceholder.typicode.com/users", {
signal: controller.signal,
})
.then((response) => setUsers(response.data))
.catch((err) => {
if (err instanceof CanceledError) return;

setError(err.message);
});

// return the clean up function


return () => controller.abort();
}, []);

return (
<>
{error && <p className="text-danger">{error}</p>}
<ul>
{users.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
</>
);
}

export default App;

/*
Sometimes, we need to return a clean up function from our effect
- in this example , we are sending an http request to the server to fetach the
users ,,,, what if the user navigates away from this page ,,,, they donot want to
wait for the server to return the data and then render it here
- so as a best practice, when we fetch the data in an effect , we should also
provide a clean up function for cancelling the request in case the data is no
longer needed

- {signal : controller.signal} ====> this 2nd parameter is called configuration


object
*/
// In summary, the cleanup function with controller.abort() is responsible for
canceling the ongoing HTTP request when the component is unmounted or when
dependencies change. The check for CanceledError in the catch block ensures that
the error is ignored if it was caused by intentional cancellation. This pattern is
useful for handling asynchronous operations in React components and avoiding
unnecessary side effects when the component is no longer in use.

******** Lesson 71 : Showing a loading indicator ********

import axios, { CanceledError } from "axios";


import { useEffect, useState } from "react";

interface User {
id: number;
name: string;
}
function App() {
const [users, setUsers] = useState<User[]>([]);
const [error, setError] = useState("");
const [isLoading, setLoading] = useState(false);

useEffect(() => {
const controller = new AbortController();

// just before we call the server , set loading


setLoading(true);
axios
.get<User[]>("https://jsonplaceholder.typicode.com/users", {
signal: controller.signal,
})
.then((response) => {
setUsers(response.data);
setLoading(false);
})
.catch((err) => {
if (err instanceof CanceledError) return;

setError(err.message);
setLoading(false);
});

// when we are done , we should set it to false so that the loader is hidden
// calling the server is an async operation (async means non blocking) ,, this
line of code will not block the execution of the code,,, so the control will
immediately move to the next line(setLoading(false)) and will end up hiding the
loader
// instead we should set the loader when our promise is settled either resolved
or rejected

/*.finally(() => {
setLoading(false);
}); */

// return the clean up function


return () => controller.abort();
}, []);

return (
<>
{error && <p className="text-danger">{error}</p>}
{ isLoading && <div className="spinner-border"></div>}
<ul>
{users.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
</>
);
}

export default App;

/*

-How to show a loading indicator when we are fetching a data

- all promises have a finally method(it wil always be executed whether our promise
is resolved or rejected) ,,,,, but this will not work when the strict mode is
on ,,,, so thats why we have duplicated this
*/

************ Lesson 72 : Deleting data ************


import axios, { CanceledError } from "axios";
import { useEffect, useState } from "react";

interface User {
id: number;
name: string;
}
function App() {
const [users, setUsers] = useState<User[]>([]);
const [error, setError] = useState("");
const [isLoading, setLoading] = useState(false);

useEffect(() => {
const controller = new AbortController();

setLoading(true);
axios
.get<User[]>("https://jsonplaceholder.typicode.com/users", {
signal: controller.signal,
})
.then((response) => {
setUsers(response.data);
setLoading(false);
})
.catch((err) => {
if (err instanceof CanceledError) return;

setError(err.message);
setLoading(false);
});
// return the clean up function
return () => controller.abort();
}, []);

const deleteUser = (user: User) => {


// restore the UI back to its original state
const originalUsers = [...users];

// uopdate the UI first


setUsers(users.filter((u) => u.id !== user.id));

// then call the server to persist the changes ,,, it will also return a
promise but .then is not needed
axios
.delete("https://jsonplaceholder.typicode.com/users/" + user.id)
.catch((err) => {
setError(err.message);
setUsers(originalUsers);
});
};

return (
<>
<p>ok</p>
{error && <p className="text-danger">{error}</p>}
{isLoading && <div className="spinner-border"></div>}
<ul className="list-group">
{users.map((user) => (
<li
key={user.id}
className="list-group-item d-flex justify-content-between"
>
{user.name}
<button
className="btn btn-outline-danger"
onClick={() => deleteUser(user)}
>
Delete
</button>
</li>
))}
</ul>
</>
);
}

export default App;


// {" "} ======> to add a white space between the user and the button ,,,
this is necessary becz when jsx is compiled , it removes all the white spaces
between Json elements (like the user and the button)

// d-flex =====> by applying this, we convert each list item to a flex container

// justify-content-between ======> we can distribute the available space between


these elements

/*

optimistic update : update the UI to give the user instant feedback then call
the server to save the changes
we are optimistic that the call to the server will succeed most of the time
*/

/*
pessimistic update: we assume that the call to the server will fail , so first
we call the server and wait for the result,,, if the call is successful then we
will update the UI
(a little bit slow)

*/

******** Lesson 73 : Creating data ***********

import axios, { CanceledError } from "axios";


import { useEffect, useState } from "react";

interface User {
id: number;
name: string;
}
function App() {
const [users, setUsers] = useState<User[]>([]);
const [error, setError] = useState("");
const [isLoading, setLoading] = useState(false);

useEffect(() => {
const controller = new AbortController();

setLoading(true);
axios
.get<User[]>("https://jsonplaceholder.typicode.com/users", {
signal: controller.signal,
})
.then((response) => {
setUsers(response.data);
setLoading(false);
})
.catch((err) => {
if (err instanceof CanceledError) return;

setError(err.message);
setLoading(false);
});

// return the clean up function


return () => controller.abort();
}, []);

const deleteUser = (user: User) => {


// restore the UI back to its original state
const originalUsers = [...users];

// uopdate the UI first


setUsers(users.filter((u) => u.id !== user.id));
// then call the server to persist the changes ,,, it will also return a
promise but .then is not needed
axios
.delete("https://jsonplaceholder.typicode.com/users/" + user.id)
.catch((err) => {
setError(err.message);
setUsers(originalUsers);
});
};

const addUser = () => {


const originalUsers = [...users];
// optimistic update
// in real world app, the properties would be based on form but here we are
hard coding them
const newUser = { id: 0, name: "Mosh" };
setUsers([newUser, ...users]);
// now call the server to save the changes ,, we should include new user in the
body of the request
axios
.post("https://jsonplaceholder.typicode.com/users", newUser)
// .then((res) => setUsers([res.data, ...users]));
// data: savedUser ==> this is just an alliace
.then(({ data: savedUser }) => {
setUsers([savedUser, ...users]);
// console.log(savedUser); ==> savedUser is not the user you've added but
rather the data returned by the server in response to the POST request, which is
assumed to include information about the newly created user.
})
.catch((err) => {
setError(err.message);
setUsers(originalUsers);
// restore the list to its original state
});
};

return (
<>
<p>ok</p>
{error && <p className="text-danger">{error}</p>}
{isLoading && <div className="spinner-border"></div>}
<button className="btn btn-primary mb-3" onClick={addUser}>
Add
</button>
<ul className="list-group">
{users.map((user) => (
<li
key={user.id}
className="list-group-item d-flex justify-content-between"
>
{user.name}
<button
className="btn btn-outline-danger"
onClick={() => deleteUser(user)}
>
Delete
</button>
</li>
))}
</ul>
</>
);
}

export default App;


/*

- if the call to the server is successful , we should refresh the list with the
saved user becz the new user has an id that is generated on the server
-res.data so the new user object will be included in the body of the response

res.data is not the user you've added but rather the data returned by the server in
response to the POST request, which is assumed to include information about the
newly created user.

- payload => body of request


- preview => body of response

*/

******** Lesson 74 : UpdatingData **********


import axios, { CanceledError } from "axios";
import { set } from "immer/dist/internal";
import { useEffect, useState } from "react";

interface User {
id: number;
name: string;
}
function App() {
const [users, setUsers] = useState<User[]>([]);
const [error, setError] = useState("");
const [isLoading, setLoading] = useState(false);

useEffect(() => {
const controller = new AbortController();

setLoading(true);
axios
.get<User[]>("https://jsonplaceholder.typicode.com/users", {
signal: controller.signal,
})
.then((response) => {
setUsers(response.data);
setLoading(false);
})
.catch((err) => {
if (err instanceof CanceledError) return;

setError(err.message);
setLoading(false);
});
return () => controller.abort();
}, []);

const deleteUser = (user: User) => {


const originalUsers = [...users];

setUsers(users.filter((u) => u.id !== user.id));

axios
.delete("https://jsonplaceholder.typicode.com/users/" + user.id)
.catch((err) => {
setError(err.message);
setUsers(originalUsers);
});
};

const addUser = () => {


const originalUsers = [...users];

const newUser = { id: 0, name: "Mosh" };


setUsers([newUser, ...users]);

axios
.post("https://jsonplaceholder.typicode.com/users", newUser)

.then(({ data: savedUser }) => {


setUsers([savedUser, ...users]);
})
.catch((err) => {
setError(err.message);
setUsers(originalUsers);
});
};

const updateUser = (user: User) => {


const originalUsers = [...users];
// update UI first
const updatedUser = { ...user, name: user.name + "!" };
setUsers(users.map((u) => (u.id === user.id ? updatedUser : u)));
// now call the server to save the changes
//we need to pass the updated user in the body of the request
// if the call to the server is successful , there is nothing we wanto to do
but if the call is not successful then we need to catch the error
axios
.patch(
"https://jsonplaceholder.typicode.com/users/" + user.id,
updatedUser
)
.catch((err) => {
setError(err.message);
setUsers(originalUsers);
});
};

return (
<>
<p>ok</p>
{error && <p className="text-danger">{error}</p>}
{isLoading && <div className="spinner-border"></div>}
<button className="btn btn-primary mb-3" onClick={addUser}>
Add
</button>
<ul className="list-group">
{users.map((user) => (
<li
key={user.id}
className="list-group-item d-flex justify-content-between"
>
{user.name}
<div>
{" "}
<button
className="btn btn-outline-secondary mx-1"
onClick={() => updateUser(user)}
>
Update
</button>
<button
className="btn btn-outline-danger"
onClick={() => deleteUser(user)}
>
Delete
</button>
</div>
</li>
))}
</ul>
</>
);
}

export default App;


// on real world app , we need a form for updating user
//mx-1 => margin horizontal 1
//in http, we use the put method for replacing an object and the patch method for
patching or updating one or more of its properties
// in preview, we have the object/response that is returned by the server

********* Lesson 75 : Extracting a reusable API client *********

***** api-client.ts*****

import axios, {CanceledError} from "axios";


// we will create a new axios client with a custom configuration

// give it a configuration object


/* these headers will be passed with every http request ,,, sometimes it is
necessary example: some backends require us to send an API key with every http
request
headers: {
'api-key': '...'
} (if this is the case, then do add this property also)
*/
export default axios.create({
baseURL: 'https://jsonplaceholder.typicode.com'

})

// export it as a named object


export {CanceledError}

**** APP.tsx ******

import { useEffect, useState } from "react";


import apiClient, { CanceledError } from "./services/api-client";

interface User {
id: number;
name: string;
}
function App() {
const [users, setUsers] = useState<User[]>([]);
const [error, setError] = useState("");
const [isLoading, setLoading] = useState(false);

useEffect(() => {
const controller = new AbortController();

setLoading(true);
apiClient
.get<User[]>("/users", {
signal: controller.signal,
})
.then((response) => {
setUsers(response.data);
setLoading(false);
})
.catch((err) => {
if (err instanceof CanceledError) return;

setError(err.message);
setLoading(false);
});

return () => controller.abort();


}, []);

const deleteUser = (user: User) => {


const originalUsers = [...users];

setUsers(users.filter((u) => u.id !== user.id));

apiClient.delete("/users/" + user.id).catch((err) => {


setError(err.message);
setUsers(originalUsers);
});
};
const addUser = () => {
const originalUsers = [...users];

const newUser = { id: 0, name: "Mosh" };


setUsers([newUser, ...users]);

apiClient
.post("/users", newUser)

.then(({ data: savedUser }) => {


setUsers([savedUser, ...users]);
})
.catch((err) => {
setError(err.message);
setUsers(originalUsers);
});
};

const updateUser = (user: User) => {


const originalUsers = [...users];
const updatedUser = { ...user, name: user.name + "!" };
setUsers(users.map((u) => (u.id === user.id ? updatedUser : u)));
apiClient.patch("/users/" + user.id, updatedUser).catch((err) => {
setError(err.message);
setUsers(originalUsers);
});
};
return (
<>
<p>ok</p>
{error && <p className="text-danger">{error}</p>}
{isLoading && <div className="spinner-border"></div>}
<button className="btn btn-primary mb-3" onClick={addUser}>
Add
</button>
<ul className="list-group">
{users.map((user) => (
<li
key={user.id}
className="list-group-item d-flex justify-content-between"
>
{user.name}
<div>
{" "}
<button
className="btn btn-outline-secondary mx-1"
onClick={() => updateUser(user)}
>
Update
</button>
<button
className="btn btn-outline-danger"
onClick={() => deleteUser(user)}
>
Delete
</button>
</div>
</li>
))}
</ul>
</>
);
}

export default App;


// we are repeating the url, to improve this we are creating a separate module
where we store our default configuration settings for making http calls
// services folder => we will add basic modules that provides services and
functionalities to our application ,,,,,, services are not about UI , they are
about functionality

// where ever yoiu have used axios, replace it with apiClient


// with this , anywhere we need to talk to our backend we simply import apiClient
and send http requests
//The . ts file extension is used when you are creating functions, classes,
reducers, etc., that do not require the use of JSX syntax and elements, whereas the
. tsx file extension is used when you create a React component and use JSX elements
and syntax.

*********** Lesson 76 : Extracting the user service ***********

***** api-client.ts****

import axios, {CanceledError} from "axios";

export default axios.create({


baseURL: 'https://jsonplaceholder.typicode.com'

})

// export it as a named object


export {CanceledError}

****** user-service.ts ******


import apiClient from "./api-client";

export interface User {


id: number;
name: string;
}
class UserService{

getAllUsers() {
const controller = new AbortController();
// the following returns a promise
const request = apiClient
.get<User[]>("/users", {
signal: controller.signal,
})
return{request, cancel: () => controller.abort()}
}

deleteUser(id: number) {
return apiClient.delete("/users/" + id)

addUser(user: User){
return apiClient.post("/users", user)

updateUser(user: User) {
return apiClient.patch("/users/" + user.id, user)

}
//export a new instance of this class as a default object
export default new UserService()

****** app.tsx *******

import { useEffect, useState } from "react";


import { CanceledError } from "./services/api-client";
import userService, { User } from "./services/user-service";

function App() {
const [users, setUsers] = useState<User[]>([]);
const [error, setError] = useState("");
const [isLoading, setLoading] = useState(false);

useEffect(() => {
setLoading(true);
const { request, cancel } = userService.getAllUsers();
request
.then((response) => {
setUsers(response.data);
setLoading(false);
})
.catch((err) => {
if (err instanceof CanceledError) return;

setError(err.message);
setLoading(false);
});

return () => cancel();


}, []);

const deleteUser = (user: User) => {


const originalUsers = [...users];

setUsers(users.filter((u) => u.id !== user.id));

userService.deleteUser(user.id).catch((err) => {
setError(err.message);
setUsers(originalUsers);
});
};

const addUser = () => {


const originalUsers = [...users];

const newUser = { id: 0, name: "Mosh" };


setUsers([newUser, ...users]);

userService
.addUser(newUser)
.then(({ data: savedUser }) => {
setUsers([savedUser, ...users]);
})
.catch((err) => {
setError(err.message);
setUsers(originalUsers);
});
};

const updateUser = (user: User) => {


const originalUsers = [...users];
const updatedUser = { ...user, name: user.name + "!" };
setUsers(users.map((u) => (u.id === user.id ? updatedUser : u)));
userService.updateUser(updatedUser).catch((err) => {
setError(err.message);
setUsers(originalUsers);
});
};
return (
<>
<p>ok</p>
{error && <p className="text-danger">{error}</p>}
{isLoading && <div className="spinner-border"></div>}
<button className="btn btn-primary mb-3" onClick={addUser}>
Add
</button>
<ul className="list-group">
{users.map((user) => (
<li
key={user.id}
className="list-group-item d-flex justify-content-between"
>
{user.name}
<div>
{" "}
<button
className="btn btn-outline-secondary mx-1"
onClick={() => updateUser(user)}
>
Update
</button>
<button
className="btn btn-outline-danger"
onClick={() => deleteUser(user)}
>
Delete
</button>
</div>
</li>
))}
</ul>
</>
);
}

export default App;


// issue: our component is too much concerned in making an http request , example:
it knows about AbortController(); which is purely about http (its about cancelling
requests)
// it also knows about get , post ,patch , delete
// it also knows about endpoints (which are repeated again and again)

// our component should only focus on returning some markups and handling user
interactions

******** Lecture 77 : Creating a generic http service ********

****** user-services.ts *******

import apiClient from "./api-client";


import create from "./http-service";

export interface User {


id: number;
name: string;
}

// this is the only place where we will provide an endpoint,,,,,, thiswill create
an instance of the class http-service
export default create('/users');

******* http-service.ts ********

/*
GENERIC
a servicing class for managing posts

<T> ==> T in this context is called a generic type parameter, it is a placeholder


for a type
- so when calling this method , we will supply a generic type argument like
getAll<User> or getAll<Post>
*/

import apiClient from "./api-client";

interface Entity{
id: number;
}

class HttpService{
// a property of type string
endpoint: string;
// when we create an instance (object) of this class, constructor is called
constructor(endpoint: string){
this.endpoint = endpoint;
}

getAll<T>() {
const controller = new AbortController();
// the following returns a promise
const request = apiClient
.get<T[]>(this.endpoint , {
signal: controller.signal,
})
return{request, cancel: () => controller.abort()}
}

delete(id: number) {
return apiClient.delete(this.endpoint + "/" + id)
}

// we need to add T as a generic type parameter

add<T>(entity: T){
return apiClient.post(this.endpoint, entity)

update<T extends Entity>(entity: T) {


return apiClient.patch(this.endpoint + "/" + entity.id, entity)

}
//export a function for creating an instance of this class
const create = (endpoint: string) => new HttpService (endpoint)
export default create

// entity which are instances of type T


// we need to tell the ts compiler that objects of type T should have a property
called id (we will do this by defining an interface)

// <T extends Entity> ===> objects of type T should extends the Entity interface
(meaning they should have an id property)

********** api-client.ts ***********

import axios, {CanceledError} from "axios";

export default axios.create({


baseURL: 'https://jsonplaceholder.typicode.com'

})

// export it as a named object


export {CanceledError}
******* App.tsx ***********
import { useEffect, useState } from "react";
import { CanceledError } from "./services/api-client";
import userService, { User } from "./services/user-service";

function App() {
const [users, setUsers] = useState<User[]>([]);
const [error, setError] = useState("");
const [isLoading, setLoading] = useState(false);

useEffect(() => {
setLoading(true);
// we should specify the type of object we are going to fetch from the server
const { request, cancel } = userService.getAll<User>();
request
.then((response) => {
setUsers(response.data);
setLoading(false);
})
.catch((err) => {
if (err instanceof CanceledError) return;

setError(err.message);
setLoading(false);
});

return () => cancel();


}, []);

const deleteUser = (user: User) => {


const originalUsers = [...users];

setUsers(users.filter((u) => u.id !== user.id));

userService.delete(user.id).catch((err) => {
setError(err.message);
setUsers(originalUsers);
});
};

const addUser = () => {


const originalUsers = [...users];

const newUser = { id: 0, name: "Mosh" };


setUsers([newUser, ...users]);

userService
.add(newUser)
.then(({ data: savedUser }) => {
setUsers([savedUser, ...users]);
})
.catch((err) => {
setError(err.message);
setUsers(originalUsers);
});
};
const updateUser = (user: User) => {
const originalUsers = [...users];
const updatedUser = { ...user, name: user.name + "!" };
setUsers(users.map((u) => (u.id === user.id ? updatedUser : u)));
userService.update(updatedUser).catch((err) => {
setError(err.message);
setUsers(originalUsers);
});
};
return (
<>
<p>ok</p>
{error && <p className="text-danger">{error}</p>}
{isLoading && <div className="spinner-border"></div>}
<button className="btn btn-primary mb-3" onClick={addUser}>
Add
</button>
<ul className="list-group">
{users.map((user) => (
<li
key={user.id}
className="list-group-item d-flex justify-content-between"
>
{user.name}
<div>
{" "}
<button
className="btn btn-outline-secondary mx-1"
onClick={() => updateUser(user)}
>
Update
</button>
<button
className="btn btn-outline-danger"
onClick={() => deleteUser(user)}
>
Delete
</button>
</div>
</li>
))}
</ul>
</>
);
}

export default App;

********* Lesson 78 : Creating a custom data fetching hook *********

********* api-client.ts *********

import axios, {CanceledError} from "axios";

export default axios.create({


baseURL: 'https://jsonplaceholder.typicode.com'
})

// export it as a named object


export {CanceledError}

******* useUsers.ts **********


// by convention, all hooks should be start with the word "use"

import { useEffect, useState } from "react";


import userService, { User } from "../services/user-service";
import { CanceledError } from "../services/api-client";

const useUsers = () => {

const [users, setUsers] = useState<User[]>([]);

const [error, setError] = useState("");


const [isLoading, setLoading] = useState(false);

useEffect(() => {
setLoading(true);

const { request, cancel } = userService.getAll<User>();


request
.then((response) => {
setUsers(response.data);
setLoading(false);
})
.catch((err) => {
if (err instanceof CanceledError) return;

setError(err.message);
setLoading(false);
});

return () => cancel();


}, []);
return {users, error , isLoading, setUsers, setError}
}

export default useUsers;

******** app.tsx ********


import { useEffect, useState } from "react";
import { CanceledError } from "./services/api-client";
import userService, { User } from "./services/user-service";
import useUsers from "./hooks/useUsers";

function App() {
// this returns an object and we can destructure it
// we are using a custom hook to fetch a list of users
const { users, error, isLoading, setUsers, setError } = useUsers();

const deleteUser = (user: User) => {


const originalUsers = [...users];
setUsers(users.filter((u) => u.id !== user.id));

userService.delete(user.id).catch((err) => {
setError(err.message);
setUsers(originalUsers);
});
};

const addUser = () => {


const originalUsers = [...users];

const newUser = { id: 0, name: "Mosh" };


setUsers([newUser, ...users]);

userService
.add(newUser)
.then(({ data: savedUser }) => {
setUsers([savedUser, ...users]);
})
.catch((err) => {
setError(err.message);
setUsers(originalUsers);
});
};

const updateUser = (user: User) => {


const originalUsers = [...users];
const updatedUser = { ...user, name: user.name + "!" };
setUsers(users.map((u) => (u.id === user.id ? updatedUser : u)));
userService.update(updatedUser).catch((err) => {
setError(err.message);
setUsers(originalUsers);
});
};
return (
<>
<p>ok</p>
{error && <p className="text-danger">{error}</p>}
{isLoading && <div className="spinner-border"></div>}
<button className="btn btn-primary mb-3" onClick={addUser}>
Add
</button>
<ul className="list-group">
{users.map((user) => (
<li
key={user.id}
className="list-group-item d-flex justify-content-between"
>
{user.name}
<div>
{" "}
<button
className="btn btn-outline-secondary mx-1"
onClick={() => updateUser(user)}
>
Update
</button>
<button
className="btn btn-outline-danger"
onClick={() => deleteUser(user)}
>
Delete
</button>
</div>
</li>
))}
</ul>
</>
);
}

export default App;

/*

imagine we have a component in which we need to fetch a list of users from the
server ,,,, if this is the case then there will be a lot of code duplication
- This is where we can use custom hook to share functionality across different
components
- a hook is just a function so we can move all this logic we are going to reuse
across components into a custom hook or a custom function

- now if we had another component , where we want to show a list of users, we can
simply reuse this custom hook :::: const {users, error, isLoading, setUsers,
setError } = useUsers()
- using custom hook , we can share functionality across different components
*/

// rest files are same as lecture 77

********* LEsson 80 ***********

we should first initialize git repositiry so that we can version our code
* first open the created folder in vs code then follow the following steps

1) open terminal
2) git init
3) git add . ( to add all the changes in the current directory to the staging
area)
4) git commit -m "Initial commit"

so now all our files are in a git repository . As we write code and make changes to
this project , we make new commit to version our code

********* Lesson 81 : installing chakra UI *********


it gives us a bunch of beautiful and reusable react components

*** now we should commit our changes to the git repository ,,,, before commiting
my chnges , I always review my code to make sure i have not made any mistakes.
*** in the very left of the vs code, click on that specific icon ,then type any
meaningful msg that identifies these changes and click commit
*** to see all the commits ,,, open terminal and type git log --oneline ( There
is a commit id that uniquely identifies each commit)
*** each commit identifies the changes we have made to our project

*********** Lesson 82 : creating a responsive layout **********

grid components in chakra works exactly like html grid

chakra > style-system > responsive-styles

**** APP.tsx

import { Grid, GridItem, Show } from "@chakra-ui/react"

function App() {
// templateAreas to define the layout of the grid ,,, in first row we have two
columns (nav and nav) ,,,, in the second row we have two columns (aside and main)
return <Grid templateAreas={{
base: `"nav" "main"`, // mobile -> single column
lg: `"nav nav " "aside main"` // screen wider than 1024 px
}}>

<GridItem area='nav' bg='coral'>Nav</GridItem>


{/* will be rendered on larde screens and above */}
<Show above="lg">
<GridItem area='aside' bg='gold'>Aside</GridItem>
</Show>

<GridItem area='main' bg='dodgerblue'>Main</GridItem>

</Grid>
}

export default App


// remove all code present in index.css

************ Lesson 83: building a navigation bar ************

********** Lesson 84 : Implementing the dark mode ********

********** Lesson 85 : Building the color mode switch *******

****** LEsson 86 : Fetching the games *********


facf5e3cd18448d88c011ebb8a7457a3

********** Lesson 87 : CReating a custom hook for fetching games ***********

****** Lesson 88 : Building game cards ******


// make a simple skeleton and then improve it step by step
// 2xl > All such fontb sizes are defined in chakra
(search for font sizes )

****** Lesson 89 : Displaying platforms icons **********


presss ctrl+T, now in the search bar we can search for classes , interface

*** for icons , use the following library


npm i [email protected]

******** Lesson 90 : Displaying critic score *********

******** Lesson 91 : Getting optimized images *********

******** Lesson 92 : Loading skeletons ***********

********* Lesson 93: Refactor: Removing Duplicated Styles

to wrap:
first select the code
shift+ CTRL+ P

******** Lesson 94 : Fetching the Genres *********

******* Lesson 95 : Creating a generic data fetching hook *********

******* Lesson 96 : Displaying the genres ********

******* Lesson 97 : showing a spinner **********

********* LEsson 98 : Filtering by genre ********


> we need to share the selected genre to our game grid
to share a state between two components, we should lift it up to the closest parent
> in app component , we have the genre list and the game grid
> in app component, create a state variable for storing the selected genre
>when we select the genre, our genre list component should notify, the app
component to set the selected genre
>the component that hold state, should be the one updating it
> now we need to pass it to the game grid so that it can be passed to the backend
while fetching the games

*********** Lesson 99 : Highlighting the selected genre ***********

********* Lesson 100 : Building platform selector *********

******* Lesson 101 : Filtering games by Platform ********


to check the value of state variable:
components tab > app

******** Lesson 102 : Refactoring: Extracting a query object ********

// we should pack related variables inside an object. So, here we will use query
object pattern
******* Lesson 103: Building sort selector *********

******* Lesson 104: Sorting Games *******


******* Lesson 105: Handling games without an image *******

to handle games without an image : we will use a default placeholder

****** Lesson 106: Fixing the issue with Chakra Menus ******

***** LEsson 107: Building search input ****


***** Lesson 108: Searching games *****
> When the user types and presses something in the search bar, this component
notifies the app component and then app component take the search text and store it
in a query object and pass it to the game grid.

> Since searchinput is not the direct child of the app component, app component
will pass that function to the navbar and the navbar will simply pass it down to
the search input

****** Lesson 109: adding a dynamic heading ******


****** Lesson 110: cleaning up the genres *******
> buttons by default do not wrap text

****** Lesson 111: Cleaning up the game cards ******


****** Lesson 112: Ading emojis ********

****** Lesson 113: Shipping static data ******


// we can treat the list of genres as static , in this way we do not need to make
any extra request to the server

****** Lesson 114: customizing the Chakra theme ******

chakra > styled systems > default theme

// we should replace the entire gray pallette with a new one


// we can use tools mentioned in chakra (on default theme page) such as themera,
smart swatch etc

***** Lecture 115: refactoring the game grid *******

***** Lesson 116: Building for production *******


1) first we have to bulid it locally to make sure that it does not have any errors.
2) npm run build
* if it run properly, it means that our application was successfully build(no
errors).
* if there is an error in the program, it will be displayed in the terminal .
(Typescript compiler tells us that there is a compile time error )
ANOTHER METHOD ( building using command pallette)
shift+ctrl+P
search for build and then execute RUN BUILD TASK
then select npm: Build

the output of build is a folder "dist" (distributable),,,all of the assests,


images, JS and css files are included in this folder

******** Lesson 117: Deploying to vercel *********


> vitejs.dev/guide/ > deploying a static site

* before deploying the application, create a git hub repository. So everytime I do


a push to this repository , vercel grabs the latest code, builds it and deploy it
to the production
* push an existing repository from the command line
(grab the commands and run in the terminal)
token (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)
so now your code is on git hub
(If this creates a problem then create a new token and run the following commands

git remote set-url origin https://USERNAME:[email protected]/N-Fatima1258/game-


hub.git
Replace USERNAME with your GitHub username and TOKEN with the personal access token
you generated.

* Retry the push command:

git push -u origin main

(Now your code is on git hub , refresh the github page)

* to install vercel cli


run
npm i -g vercel

************** 1 METHOD ***********


{ * run
vercel then select login with github
* keep the default settings and keep pressing enter untill your project is deployed
and you get a link
* now link the project to github repository
open vercel , you will see your project , click on the link (which is under your
project name)
* click on connect git repository
then click on git hub
then click on connect (according to your project) }

*********** 2 METHOD ************


{
* open the vercel dashboard
* click on add new and then click on project
* import the project
*click on deploy
* clcik on continue to dashboard
* under the domain name : you have the link of your app
}

* now if we do any other push to this repo, vercel will grab the code, built it and
deploy it to production
* to verify , make another commit and then open terminal and write git push
*********** PART # 2 ::: REACT INTERMEDIATE **********

https://github.com/mosh-hamedani/react-course-part2-starter

https://github.com/mosh-hamedani/react-course-part2-finish

https://github.com/mosh-hamedani/game-hub

https://github.com/mosh-hamedani/game-hub-part2-finish

Lesson 3:
DATA MANAGEMENT AND CACHING:
react query:library for data management(managing data fetching) and caching in
react applications
with react query you donot have to use effect hook to fectch data , catch errors or
loading indicators.

ADDITIONAL FEATURES:
caching, automatic retry, automatic refresh, paginated queries, infinite queries

GLOBAL STATE MANAGEMENT:


reducers, context,providers,zustand(state management library)

ROUTING WITH REACT ROUTER:


enables us to build multi page applications

(the more you code and experiment with what you have built, you become more skilled
and confident.

******* LEsson 7: What is react query ******


>PROBLEMS
* we use effect hook to execute code that has side effects.
* clean up function for unduing what we did before. In case of http request, we
should cancel them.
*no separation of concerns: the quering logic is leaked into the component. If
somewhere else we need the same piece of data , we have to duplicate the quering
logic, so there is no opportunity of reuse here. There is no modularity in the
code.

* no automatic retry : if there is an error, we show an error msg to the user and
move on
*no automatic refresh: if the data changes while the user is on the page , they
donot see the changes unless the user refreshes the page

* there is no caching

CACHING: the process of storing data in a place where it can be accessed more
quickly and efficiently in the future.
FOR EXAMPLE : we can store frequently used data on the client(inside the users
browser), so we donot have to fetch it from the server everytime it is needed
to solve it (extract the query logic in a hook)

> redux VS react query

***** Lesson 8 : Setting up react query ********


npm i @tanstack/[email protected]
* import certain classes in main.tsx
import {QueryClient, QueryClientProvider} from '@tanstack/react-query'
> query client is the core object we use for managing and caching remote data in
react
> create a new instance of query client in main.tsx

const queryClient = new QueryClient();


and then we need to pass it to our component tree using the QueryClientProvider

> then wrap the app component

<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>

*********** Lesson 9: Fetching Data ***********


******* TodoList.tsx

import { useQuery } from "@tanstack/react-query";


import axios from "axios";
import React, { useEffect, useState } from "react";

interface Todo {
id: number;
title: string;
userId: number;
completed: boolean;
}

const TodoList = () => {


const fetchTodos = () =>
axios
.get<Todo[]>("https://jsonplaceholder.typicode.com/todos")
.then((res) => res.data);
const { data: todos } = useQuery({
queryKey: ["todos"],
queryFn: fetchTodos,
});
// if (error) return <p>{error}</p>;
return (
<ul className="list-group">
{todos?.map((todo) => (
<li key={todo.id} className="list-group-item">
{todo.title}
</li>
))}
</ul>
);
};

export default TodoList;


/*
>to fetch data with react query , use the query hook
> give it a configuration object with 2 properties
1) queryKey: unique identifier for the query. Its used internally for caching.
Anytime we retrieved a pice pf data from the backend, the data is stored in the
cache and will be accessible via this key
todos :the type of data that we want to store
2) queryFn: the function that we use to fetch the data from the backend

> we donot want to store the response in the cache, we want to store the actual
data that we get from the backend
> at run time , reactQuery will call this function .When the promise is resolved,
we get an array of todos . Then that array is stored in the cache against the key
['todos']

> If the call to the server fails, react query will try a couple of more times(auto
retries)
> we can configure this query to auto refresh after a period of time
> the first time we will fetch the data , it is stored in the cache and will be
refreshed for a certain period of time . The next time we need the same piece of
data, if it is still in the cache, we will not make any other request to the server
and will directly get it from the cache(improves performance)
*/

********* Lesson 10 : Handling errors **********


import { useQuery } from "@tanstack/react-query";
import axios from "axios";

interface Todo {
id: number;
title: string;
userId: number;
completed: boolean;
}

const TodoList = () => {


const fetchTodos = () =>
axios
.get<Todo[]>("https://jsonplaceholder.typicode.com/todos")
.then((res) => res.data);
const { data: todos, error } = useQuery<Todo[], Error>({
queryKey: ["todos"],
queryFn: fetchTodos,
});
if (error) return <p>{error.message}</p>;

return (
<ul className="list-group">
{todos?.map((todo) => (
<li key={todo.id} className="list-group-item">
{todo.title}
</li>
))}
</ul>
);
};

export default TodoList;

/*
> in axios, all errors are instances of Error interface that is available in all
browsers

> useQuery<Todo[],Error> ==> leave other to the default values


(type < after useQuery and you will see the details)
*/

******** Lesson 11 : Showing a loading indicator *******

import { useQuery } from "@tanstack/react-query";


import axios from "axios";

interface Todo {
id: number;
title: string;
userId: number;
completed: boolean;
}

const TodoList = () => {


const fetchTodos = () =>
axios
.get<Todo[]>("https://jsonplaceholder.typicode.com/todos")
.then((res) => res.data);
const {
data: todos,
error,
isLoading,
} = useQuery<Todo[], Error>({
queryKey: ["todos"],
queryFn: fetchTodos,
});
if (isLoading) return <p>isLoading......</p>;
if (error) return <p>{error.message}</p>;

return (
<ul className="list-group">
{todos?.map((todo) => (
<li key={todo.id} className="list-group-item">
{todo.title}
</li>
))}
</ul>
);
};

export default TodoList;

/*
> with react query, we no longer have to declare separate state variables for
data , error, isLoading
*/

******** Lesson 12: Creating a custom query hook *******


SEPARATION OF CONCERNS
useTodos.ts (hook)

******* Lesson 13 : Using react query DevTools *******


> similar to other frontend librarires, react query comes with its own dev tool
(its a powerful tool for debugging)
npm i @tanstack/[email protected]
> import it in main.tsx
> add it to the component tree after the app component
> this only goes in our development build. Later when we build our app for
production, react query dev tools will not be included.
>click on that icon on browser. Content of the cache (item with a key)

> no. of abservers refers to the compnents that are using this query
> if the component is unmounted, then the number of observers will be 0 and the
query will be inactive. Inactive queries (if a query has no observer meaning no
component is using that query) get garbage collective and remove from the cache
after 5 minutes.

> Query explorer window: where you can see all the properties of the query
> all the queries have a default rime of 300 thousand mili s which is 5 min

********* Lesson 14: Customizing query settings *********


>>>> main.tsx

import "bootstrap/dist/css/bootstrap.css";
import React from "react";
import ReactDOM from "react-dom/client";
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import App from "./App";
import "./index.css";
// > We can overwrite the default settings of our query globally
// > pass a configuration object
//>
// staleTime: how long the data is considered fresh
const queryClient = new QueryClient({
defaultOptions: {
queries: {
retry: 3,
cacheTime: 300_000,
staleTime: 10 * 1000, //10s
refetchOnWindowFocus: false,
refetchOnReconnect: false,
refetchOnMount: false,
},
},
});

ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
<React.StrictMode>
<QueryClientProvider client={queryClient}>
<App />
<ReactQueryDevtools />
</QueryClientProvider>
</React.StrictMode>
);
/*
> react query will automatically refreshes the stale data under 3 situations:
1. when the network is reconnected
2. when a component is mounted
3. when the window is refocused

(we can disable the above features)

> when the data gets stale(old), reactQuery will refetch it from the backend while
at the same time, returning the stale data from the cache to the application
Once we get the updated data, reactQuery will update the cache and notify the
component that new data is now available. Our component will then rerender with
fresh data
*/
/*
> we can alse set the default settings per query
such as in useTodos.ts:
return useQuery<Todo[], Error>({
queryKey: ["todos"],
queryFn: fetchTodos,
staleTime: 10 * 1000
});
}
*/

********* Lesson 15 : exercise Fetching Data *********


PostList.tsx
usePosts.ts
*********** Lesson 16: Parameterized queries ***********
How we can fetch hierarchical or nested resource

>>>>> PostList.tsx

import { useState } from "react";


import usePosts from "./hooks/usePosts";

const PostList = () => {


const [userId, setUserId] = useState<number>();

const { data: posts, error, isLoading } = usePosts(userId);


if (isLoading) return <p>Loading....</p>;
if (error) return <p>{error.message}</p>;

return (
<>
<select
className="form-select mb-3"
onChange={(event) => setUserId(parseInt(event.target.value))}
value={userId}
>
<option value=""></option>
<option value="1">User 1</option>
<option value="2">User 2</option>
<option value="3">User 3</option>
</select>
<ul className="list-group">
{posts?.map((post) => (
<li key={post.id} className="list-group-item">
{post.title}
</li>
))}
</ul>
</>
);
};

export default PostList;


// in real world app, we should render users dynamically
// value props in select tag : so in the next render, the right option gets
selected

>>>>> usePosts.ts
import { useQuery } from "@tanstack/react-query"
import axios from "axios";
interface Post {
id: number;
title: string;
body: string;
userId: number;
}

const usePosts = (userId: number | undefined) => {

const fetchPosts = () =>


axios
.get<Post[]>("https://jsonplaceholder.typicode.com/posts", {
params: {
userId
}
})
.then(res => res.data)

return useQuery<Post[],Error>({
queryKey: userId ? ['users', userId, 'posts'] : ['posts'],
queryFn: fetchPosts,
staleTime: 1 * 60 * 1000 //1min
})
}
export default usePosts;
/*
queryKey: ['users', userId, 'posts'],
> Similar like api for fetching posts for the users: /users/1/posts
> every time the userId changes, reactQuery will fetch the posts for that user from
the backend
> similar to the dependency array of the effect hook, any time any of the
dependency changes, the effect gets reexecuted. So, any time the userId changes our
query will get re executed
> ugly way : https://jsonplaceholder.typicode.com/posts?userId=1, so we use params
in params: add all the query string parameters
*/

******** Lesson 17: Paginated Queries **********


>>>>> PostList.tsx
import { useState } from "react";
import usePosts from "./hooks/usePosts";

const PostList = () => {


// page size does not change here, so we donot need state variable, can add
dropdown list so the user can select the page size(in that case we need a state
variable)
const pageSize = 10;
// to keep track of the current page
const [page, setPage] = useState(1);
const { data: posts, error, isLoading } = usePosts({ page, pageSize });
if (isLoading) return <p>Loading....</p>;
if (error) return <p>{error.message}</p>;

return (
<>
<ul className="list-group">
{posts?.map((post) => (
<li key={post.id} className="list-group-item">
{post.title}
</li>
))}
</ul>
<button
className="btn btn-primary my-3"
disabled={page === 1}
onClick={() => setPage(page - 1)}
>
Previous
</button>

<button
className="btn btn-primary my-3 ms-1"
onClick={() => setPage(page + 1)}
>
Next
</button>
</>
);
};

export default PostList;


/*
> usePosts() ===> pass a query object here

query object is an object that contain all the values for quering a set of objects
(it has nothing to so with reactQuery )
*/

>>>>>>>usePosts.ts
import { useQuery } from "@tanstack/react-query"
import axios from "axios";
interface Post {
id: number;
title: string;
body: string;
userId: number;
}
interface PostQuery{
page: number;
pageSize: number;
}

const usePosts = (query: PostQuery) => {

const fetchPosts = () =>


axios
.get<Post[]>("https://jsonplaceholder.typicode.com/posts", {
params: {
// pass values in this query object to our backend
_start: (query.page - 1) * query.pageSize,
_limit: query.pageSize
}
})
.then(res => res.data)

return useQuery<Post[],Error>({
queryKey: ['posts', query],
queryFn: fetchPosts,
staleTime: 1 * 60 * 1000, //1min
keepPreviousData: true // screen will not jump up and down
})
}
export default usePosts;
/* queryKey: ['posts', query] ===> any time the query changes, react will re
fetch the data from the backend
_start => index ofour starting position
*/

********** Lesson 18 : Infinite Queries ************


> in real world application, we want to load more data as the user scrolls down or
reaches the bottom of the page
> here we will add a load more button

>>>>>>>>>> PostList.tsx
import { useState } from "react";
import usePosts from "./hooks/usePosts";
import React from "react";

const PostList = () => {


// page size does not change here, so we donot need state variable, can add
dropdown list so the user can select the page size(in that case we need a state
variable)
const pageSize = 10;
// to keep track of the current page
const { data, error, isLoading, fetchNextPage, isFetchingNextPage } =
usePosts({ pageSize });
if (isLoading) return <p>Loading....</p>;
if (error) return <p>{error.message}</p>;

return (
<>
<ul className="list-group">
{data.pages.map((page, index) => (
<React.Fragment key={index}>
{page.map((post) => (
<li key={post.id} className="list-group-item">
{post.title}
</li>
))}
</React.Fragment>
))}
</ul>

<button
className="btn btn-primary my-3 ms-1"
disabled={isFetchingNextPage}
onClick={() => fetchNextPage()}
>
{isFetchingNextPage ? "Loading..." : "Load More"}
</button>
</>
);
};

export default PostList;


/*
> usePosts() ===> pass a query object here

query object is an object that contain all the values for quering a set of objects
(it has nothing to so with reactQuery )

> infiniteQuery have a function : getNextPageParam ==> to get the next page number
>data is an instance of infinite data

*/

>>>>>>>>>> usePosts.ts

import { useInfiniteQuery, useQuery } from "@tanstack/react-query"


import axios from "axios";
interface Post {
id: number;
title: string;
body: string;
userId: number;
}
interface PostQuery{
pageSize: number;
}

const usePosts = (query: PostQuery) => {

const fetchPosts = ({pageParam = 1}) =>


axios
.get<Post[]>("https://jsonplaceholder.typicode.com/posts", {
params: {
// pass values in this query object to our backend
_start: (pageParam - 1) * query.pageSize,
_limit: query.pageSize
}
})
.then(res => res.data)

return useInfiniteQuery<Post[],Error>({
queryKey: ['posts', query],
queryFn: fetchPosts,
staleTime: 1 * 60 * 1000, //1min
keepPreviousData: true, // screen will not jump up and down
getNextPageParam: (lastPage, allPages) => {
//1 -> 2
return lastPage.length > 0 ? allPages.length + 1 : undefined; // as a
next page number
}
})
}
export default usePosts;
/* infinite queries handle pagination automatically(we cannot use state variables
to keep track of page numbers)
the parameter (allPages: its a 2D array :an array whose each element is an
array)contain the data for all pages
> if we are on page 1, we have the single element in this array (allPages)

> If we ask data for a page that doesnot exist, we get an empty array(in JSon
placeholder). last page can be an empty array (varies for different backends)

> when the user clicks on the load more button ,fetchNextPage() function is called.
React query will calls this function (getNextPageParam) to get the next page
number. Then it will pass the page number to our query function( queryFn: ).
pageParam(destructure it)is a property in the object that reactQuery will pass to
queryFn
*/

********** Lesson 20 : Mutating data ********


> add a new TODO in the list
> mutations are typically used to create/update/delete data or perform server side-
effects.
> in the networks tab, you can see all the requests you have made to the backend

>>>>>>>>>>>>>>>>> TodoForm.tsx
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { useRef } from "react";
import { Todo } from "./hooks/useTodos";
import axios from "axios";

const TodoForm = () => {


const queryClient = useQueryClient();
const addTodo = useMutation({
mutationFn: (todo: Todo) =>
axios
.post<Todo>("https://jsonplaceholder.typicode.com/todos", todo)
.then((res) => res.data),
// if every thing goes well : (object that we get from the backend, object that
we send to the server)
onSuccess: (savedTodo, newTodo) => {
// console.log(savedTodo)
/*APPROACH1(does not work with JSON placeholder): Invalidating the cache
(tell reactQuery that what we have in the cache is invalid so reactQuery will
refetch the data from the backend)
queryCLient.invalidateQueries({
queryKey: ['todos'] // invalidate all queries whose key starts with
todos
}) */

//APPROACH2: updating the data in the cache directly


// we update data in an unmutable way(in react apps)
queryClient.setQueryData<Todo[]>(["todos"], (todos) => [
savedTodo,
...(todos || []),
]);
},
});
const ref = useRef<HTMLInputElement>(null);

return (
<form
className="row mb-3"
onSubmit={(event) => {
event.preventDefault();
// all mutation object have a mutate method (.mutate()) for mutating
data ,,, when we call this, react query will send data to the backend using the
mutationFn ,,, now we should pass a tdo object and this todo object will be the one
that we pass to the mutationFn
if (ref.current && ref.current.value)
addTodo.mutate({
id: 0, // id id generated on the backend
title: ref.current?.value,
completed: false,
userId: 1,
});
}}
>
<div className="col">
<input ref={ref} type="text" className="form-control" />
</div>
<div className="col">
<button className="btn btn-primary">Add</button>
</div>
</form>
);
};

export default TodoForm;


// used ref hook to access value in the input field

*********** Lesson 21: Handling Mutation Errors ***********


>>>>>>>>>>>>>>>>> TodoForm.tsx

import { useMutation, useQueryClient } from "@tanstack/react-query";


import { useRef } from "react";
import { Todo } from "./hooks/useTodos";
import axios from "axios";

const TodoForm = () => {


const queryClient = useQueryClient();
const addTodo = useMutation<Todo, Error, Todo>({
mutationFn: (todo: Todo) =>
axios
.post<Todo>("https://jsonplaceholder.typicode.com/todos", todo)
.then((res) => res.data),
onSuccess: (savedTodo, newTodo) => {
console.log(savedTodo);
console.log(newTodo);

queryClient.setQueryData<Todo[]>(["todos"], (todos) => [


savedTodo,
...(todos || []),
]);
},
});
const ref = useRef<HTMLInputElement>(null);

return (
<>
{addTodo.error && (
<div className="alert alert-danger">{addTodo.error.message}</div>
)}
<form
className="row mb-3"
onSubmit={(event) => {
event.preventDefault();

if (ref.current && ref.current.value)


addTodo.mutate({
id: 0,
title: ref.current?.value,
completed: false,
userId: 1,
});
}}
>
<div className="col">
<input ref={ref} type="text" className="form-control" />
</div>
<div className="col">
<button className="btn btn-primary">Add</button>
</div>
</form>
</>
);
};

export default TodoForm;


//Tvariables: the data that we send to the backend

******* Lesson 22: Showing Mutation Progress *******


>>>>>>>>>>>>>>>>> TodoForm.tsx

import { useMutation, useQueryClient } from "@tanstack/react-query";


import { useRef } from "react";
import { Todo } from "./hooks/useTodos";
import axios from "axios";

const TodoForm = () => {


const queryClient = useQueryClient();
const addTodo = useMutation<Todo, Error, Todo>({
mutationFn: (todo: Todo) =>
axios
.post<Todo>("https://jsonplaceholder.typicode.com/todos", todo)
.then((res) => res.data),
onSuccess: (savedTodo, newTodo) => {
console.log(savedTodo);
console.log(newTodo);

queryClient.setQueryData<Todo[]>(["todos"], (todos) => [


savedTodo,
...(todos || []),
]);
if (ref.current) ref.current.value = "";
},
});
const ref = useRef<HTMLInputElement>(null);

return (
<>
{addTodo.error && (
<div className="alert alert-danger">{addTodo.error.message}</div>
)}
<form
className="row mb-3"
onSubmit={(event) => {
event.preventDefault();

if (ref.current && ref.current.value)


addTodo.mutate({
id: 0,
title: ref.current?.value,
completed: false,
userId: 1,
});
}}
>
<div className="col">
<input ref={ref} type="text" className="form-control" />
</div>
<div className="col">
<button className="btn btn-primary" disabled={addTodo.isLoading}>
{addTodo.isLoading ? "Adding" : "Add"}
</button>
</div>
</form>
</>
);
};

export default TodoForm;


//Tvariables: the data that we send to the backend

*********** Lesson23 : Optimistic updates ************


> improve user experience: We can assume that most of the time, our backend calls
works fine

>>>>>>>>>>>>>>> TodoForm.tsx
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { useRef } from "react";
import { Todo } from "./hooks/useTodos";
import axios from "axios";

interface AddTodoContext {
previousTodos: Todo[];
}

const TodoForm = () => {


const queryClient = useQueryClient();
const addTodo = useMutation<Todo, Error, Todo, AddTodoContext>({
mutationFn: (todo: Todo) =>
axios
.post<Todo>("https://jsonplaceholder.typicode.com/todos", todo)
.then((res) => res.data),
// this fn is called before our mutation is executed
onMutate: (newTodo: Todo) => {
// to get todos before we update the cache
const previousTodos = queryClient.getQueryData<Todo[]>(["todos"]) || [];
// we sholud update the query cache right away
queryClient.setQueryData<Todo[]>(["todos"], (todos) => [
newTodo,
...(todos || []),
]);
if (ref.current) ref.current.value = "";
return { previousTodos };
},
// if the request is successful, we shall replace the newTodo with the one that
we get from the backend
onSuccess: (savedTodo, newTodo) => {
queryClient.setQueryData<Todo[]>(["todos"], (todos) =>
todos?.map((todo) => (todo === newTodo ? savedTodo : todo))
);
},
//if the request fails, we should roll back and restore the previous UI
// context object that include previous todos before updating the cache
// as a data we provide context.previousTodos
onError: (error, newTodo, context) => {
if (!context) return;
queryClient.setQueryData<Todo[]>(["todo"], context.previousTodos);
},
});
const ref = useRef<HTMLInputElement>(null);

return (
<>
{addTodo.error && (
<div className="alert alert-danger">{addTodo.error.message}</div>
)}
<form
className="row mb-3"
onSubmit={(event) => {
event.preventDefault();

if (ref.current && ref.current.value)


addTodo.mutate({
id: 0,
title: ref.current?.value,
completed: false,
userId: 1,
});
}}
>
<div className="col">
<input ref={ref} type="text" className="form-control" />
</div>
<div className="col">
<button className="btn btn-primary" disabled={addTodo.isLoading}>
{addTodo.isLoading ? "Adding" : "Add"}
</button>
</div>
</form>
</>
);
};

export default TodoForm;


//Tvariables: the data that we send to the backend
// variables in react query refers to the input (the data that we send to the
backend )
// context is the object that we create to pass the data in between our callbacks

// In onMutate fn we update the query cache so the UI gets updated right away. At
the end we return a context object that includes the previous data. We will use
the context in case our requests fails. In onError, we use the previous Todos or
context to update the query cache. If the request is successful we replace the
newTodo with the savedTodo that we get from the backend

********* Lesson 24: creating a Custom Mutation Hook *********


> WE should extract all the extra stuff and move it to a reusable hook
> reduce complexity of component, making it more modular
> extract the key and put it in a separate file
> our component is responsible for markup and ui logic , and hook is responsible
for data management ===> separation of concerns

>>>>>>>>>> TodoForm.tsx
import { useRef } from "react";
import useAddTodo from "./hooks/useAddTodo";

const TodoForm = () => {


const ref = useRef<HTMLInputElement>(null);
// useAddTodo hook should return a mutation object
const addTodo = useAddTodo(() => {
if (ref.current) ref.current.value = "";
});

return (
<>
{addTodo.error && (
<div className="alert alert-danger">{addTodo.error.message}</div>
)}
<form
className="row mb-3"
onSubmit={(event) => {
event.preventDefault();

if (ref.current && ref.current.value)


addTodo.mutate({
id: 0,
title: ref.current?.value,
completed: false,
userId: 1,
});
}}
>
<div className="col">
<input ref={ref} type="text" className="form-control" />
</div>
<div className="col">
<button className="btn btn-primary" disabled={addTodo.isLoading}>
{addTodo.isLoading ? "Adding" : "Add"}
</button>
</div>
</form>
</>
);
};

export default TodoForm;


// Every thing that is UI related, keep that in this component

>>>>>>>>>>>>>>>>>>> useAddTodos.ts

import { useMutation, useQueryClient } from "@tanstack/react-query";


import { Todo } from "./useTodos";
import axios from "axios";
import { CACHE_KEY_TODOS } from "../constants";
interface AddTodoContext {
previousTodos: Todo[];
}

const useAddTodo = (onAdd: () => void) => {


const queryClient = useQueryClient();
return useMutation<Todo, Error, Todo, AddTodoContext>({
mutationFn: (todo: Todo) =>
axios
.post<Todo>("https://jsonplaceholder.typicode.com/todosx", todo)
.then((res) => res.data),
onMutate: (newTodo: Todo) => {
const previousTodos = queryClient.getQueryData<Todo[]>(CACHE_KEY_TODOS) ||
[];
queryClient.setQueryData<Todo[]>(CACHE_KEY_TODOS, (todos = []) => [
newTodo,
...todos,
]);
// if (ref.current) ref.current.value = "";
onAdd(); // we let the consumer of this hook to decide what shoud happen at
this moment
return { previousTodos };
},

onSuccess: (savedTodo, newTodo) => {


queryClient.setQueryData<Todo[]>(CACHE_KEY_TODOS, (todos) =>
todos?.map((todo) => (todo === newTodo ? savedTodo : todo))
);
},

onError: (error, newTodo, context) => {


if (!context) return;
queryClient.setQueryData<Todo[]>(["todo"], context.previousTodos);
},
});
}
export default useAddTodo;

// hook should be reusable


// the purpose of this hook is to send newTodo to the backend and updating the
cache (this hook is about data management)
// move UI related logic to the consumer of this component
// we can give this hook a call back fn. So our component is gonna pass a fn, our
hook is gonna call that fn back to move control back to the component
// undefined objects are not spreadable

********** Lesson 25: Creating a reusable API Client **********


> we are duplicating endpoints
> quering logic has leaked in our hook
> implemet a reuasable apiClient
> to check error , open console click on the error then mark breakpoint on the
error line then press ctrl + R and in the scope part you will find that whats
happening

>>>>>>>>> TodoForm.tsx
import { useRef } from "react";
import useAddTodo from "./hooks/useAddTodo";
const TodoForm = () => {
const ref = useRef<HTMLInputElement>(null);
const addTodo = useAddTodo(() => {
if (ref.current) ref.current.value = "";
});

return (
<>
{addTodo.error && (
<div className="alert alert-danger">{addTodo.error.message}</div>
)}
<form
className="row mb-3"
onSubmit={(event) => {
event.preventDefault();

if (ref.current && ref.current.value)


addTodo.mutate({
id: 0,
title: ref.current?.value,
completed: false,
userId: 1,
});
}}
>
<div className="col">
<input ref={ref} type="text" className="form-control" />
</div>
<div className="col">
<button className="btn btn-primary">Add</button>
</div>
</form>
</>
);
};

export default TodoForm;

>>>>>>>>>>> useAddTodos.ts
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { Todo } from "./useTodos";
import axios from "axios";
import { CACHE_KEY_TODOS } from "../constants";
import APIClient from "../services/apiClient";

const apiClient = new APIClient<Todo>('/todos')


interface AddTodoContext {
previousTodos: Todo[];
}

const useAddTodo = (onAdd: () => void) => {


const queryClient = useQueryClient();
return useMutation<Todo, Error, Todo, AddTodoContext>({
mutationFn: apiClient.post,
onMutate: (newTodo: Todo) => {
const previousTodos = queryClient.getQueryData<Todo[]>(CACHE_KEY_TODOS) ||
[];
queryClient.setQueryData<Todo[]>(CACHE_KEY_TODOS, (todos = []) => [
newTodo,
...todos,
]);
// if (ref.current) ref.current.value = "";
onAdd();
return { previousTodos };
},

onSuccess: (savedTodo, newTodo) => {


queryClient.setQueryData<Todo[]>(CACHE_KEY_TODOS, (todos) =>
todos?.map((todo) => (todo === newTodo ? savedTodo : todo))
);
},

onError: (error, newTodo, context) => {


if (!context) return;
queryClient.setQueryData<Todo[]>(["todo"], context.previousTodos);
},
});
}
export default useAddTodo;

>>>>>>>>>>>>>>>>>> useTodos.ts
import { useQuery } from "@tanstack/react-query";
import APIClient from "../services/apiClient";
import { CACHE_KEY_TODOS } from "../constants";

// create instance of this class


const apiClient = new APIClient<Todo>('/todos');

export interface Todo {


id: number;
title: string;
userId: number;
completed: boolean;
}

const useTodos = () => {

return useQuery<Todo[], Error>({


queryKey: CACHE_KEY_TODOS,
queryFn: apiClient.getAll,
staleTime: 10 * 1000
});
}
export default useTodos
//apiClient.getAll ==> just reference it , donot call it. At runtime, when we need
to fetch data , reactQuery will call this function

>>>>>>>>>>> apiClient.ts
import axios from "axios";
const axiosInstance = axios.create({
baseURL: 'https://jsonplaceholder.typicode.com'
})

// to send various type of http request to a particular endpoint


class APIClient<T>{
endpoint: string;

constructor(endpoint: string) {
this.endpoint = endpoint;
}

//methods to make http requests


getAll = () => {
// debug
// debugger; // when we run our app, the execution stops right here
return axiosInstance
.get<T[]>(this.endpoint)
.then(res => res.data)
}
post = (data: T) => {
return axiosInstance
.post<T>(this.endpoint, data)
.then(res => res.data)
}

}
export default APIClient;// export APIClient class

// getAll<T>() => duplicating the generic type parameter(<T>)so add it while


defining the class
// arrow fn donot have their own this context ,,, this context inside arrow fn will
refer to the apiClient instance

********* Lesson 26: creating a reusable Http service *********


> we have duplicated the instance of aPIClient class in useTodos and useAddTodos

> created todoService.ts file

****** Lesson 27: Understanding the application layers ******


components => TodoForm,TodoList
custom hooks => useTodos, useAddTodos
http service => todoService
API Client => APIClient

(down to up)

> each layer in the app has a single responsibility, resulting in a clean and a
well organized architecture
> by breaking down our app in these layers, we can easily manage and maintain our
code, reduce duplication and improve scalability

****** Lesson 28: *********


> implementing caching and infinite scrolling in the game hub project

******** Lesson 29: Exercise: Fetching Genres *********


********* LEsson 30: Exercise: Fetching platforms *********
> Ctrl + Shift + P and then write organize imports
********* Lesson 31: Exercise: Fetching Games ********
> to make sure that there are no errors anywhere else, build the project with TS
compiler
> bring up the command pallette (ctrl+ shift+ P) and search for build.
> Select run build task and then select npm: build (tsc && vite build)

******* Lesson 32: Exercise: Removing duplicate interfaces ******


before removing any block or file etc, fing=d references to it(click on that
particular thing and then right click and then select references)

****** Lesson 33: Exercise: Creating a Reusable API Client *******

****** Lesson 34: Exercise: Implementing infinite queries *******


Ctrl + T => you can search for any interface, class etc

******* Lesson 35 : Implementing infinite scroll ********


popular library: react-infinit-scroll-component
> npm i [email protected]

********** Lesson 36: Simplifying query objects **********


> Before making any changes, we should evaluate the impact of this change. Example:
(right click and go to references)
> undefined: the absence of a value
> null: the intentional absence of a value

******** Lesson 37: Creating Looking up Hooks *********


> avoid any sort of duplication
> usePlatform hook
> useGenre.ts hook

***** Lesson 38: simplifying Time Calculations **********

npm i [email protected]
> The above library does not include the type declaration that the TS compiler
needs, so we need to install them separately( we need types only for development
not for production),,, so install it as a development dependency
npm i -D @types/ms

********* Lesson 39 : Global state management *********


> consolidating state logic with a reducer
> sharing data using react context
> when to use react context
> react context VS Redux
> Managing application state using ZUSTAND

********** Lesson 40 : Consolidating State logic with a Reducer ***********

" JavaScript (JS) Consolidation involves taking multiple JavaScript files called by
a page and consolidating them into fewer JS files. Fewer resources from the server
generally result in less latency and fewer resource requests for the client. "
REDUCER: " A function that allows us to centralize state updates in a component. "

> using a reducer, we can take the state management logic outside of the
componentand centralize it inside a single function

// component is responsible for markup only , it does not have any state management
logic : separation of concerns , we can reuse this reducer in another component
that works with counter

>>>>>>>>>>>>>>>>Counter.tsx

import { useReducer, useState } from "react";


import counterReducer from "./reducers/counterReducer";

const Counter = () => {


// const [value, setValue] = useState(0);
/* Reducer hook defined in react (reducerFn, initial state)
This hook returns an array with 2 elements [current state, fn for triggering
changes]
> dispact => the type of this parameter is the Action INTERFACE that we just
defined
> by dispatching an action, we are telling react that the user is trying to
increment the counter,,, at this momement react will call the reducer and pass the
current state or current value as well as the action that we just dispatched
> in the reducer, we check the action and return the next state back to the
component
*/
const [value, dispatch] = useReducer(counterReducer, 0);

return (
<div>
Counter ({value})
<button
onClick={() => dispatch({ type: "INCREMENT" })}
className="btn btn-primary mx-1"
>
Increment
</button>
<button
onClick={() => dispatch({ type: "RESET" })}
className="btn btn-primary mx-1"
>
Reset
</button>
</div>
);
};

export default Counter;

>>>>>>>>>>>>>>>>>>>>>counterReducer.ts
/*
> a reducer fn should have 2 parameters (state, action)
an action is an object that defines what the user is trying to do
> a reducer takes the current state and action and returns the new state
> there are no rules for annotating action (we can simply annotate it as a string)
but by convention, we use an object with a type property that describes the action
> we can annotate this function with a return value
> if (action.type === 'INCREMENT'): this is an arbitrary value, we could use any
values here as long as the value clearly defines the action
*/
interface Action{
type: 'INCREMENT' | 'RESET' // type = union of literal values
}

const counterReducer = (state: number, action: Action): number => {


if (action.type === 'INCREMENT') return state + 1;
if(action.type === 'RESET') return 0;
// if action is of different type( none of the above)
return state;
}
export default counterReducer;

********** Lesson 41: Creating Complex Actions **********

>>>>>> TaskList.tsx

import { useReducer, useState } from "react";


import tasksReducer from "./reducers/tasksReducer";

const TaskList = () => {


// const [tasks, setTasks] = useState<Task[]>([]);
const [tasks, dispatch] = useReducer(tasksReducer, []);

return (
<>
<button
onClick={() =>
dispatch({
type: "ADD",
task: { id: Date.now(), title: "Task" + Date.now() },
})
}
className="btn btn-primary my-3"
>
Add Task
</button>
<ul className="list-group">
{tasks.map((task) => (
<li
key={task.id}
className="list-group-item d-flex justify-content-between align-items-
center"
>
<span className="flex-grow-1">{task.title}</span>
<button
className="btn btn-outline-danger"
onClick={() => dispatch({ type: "DELETE", taskId: task.id })}
>
Delete
</button>
</li>
))}
</ul>
</>
);
};

export default TaskList;

>>>>>>>>>>>>>>>>>> tasksReducer.ts

interface Task {
id: number;
title: string;
}

interface AddTask {
type: 'ADD';
task: Task; // PayLoad
}
interface DeleteTask {
type: 'DELETE';
taskId: number; // PayLoad
}

// union of above two interfaces


type TaskAction = AddTask | DeleteTask;

// tasks or state
const tasksReducer = (tasks: Task[], action: TaskAction ): Task[] => {
switch (action.type) {
case 'ADD':
return [action.task, ...tasks];
case 'DELETE':
return tasks.filter(t => t.id !== action.taskId);

export default tasksReducer;

******** Lesson 42: Exercise: Working with REDUCERS ********


>>>>>>>>>>>>>>>>> LoginStatus.tsx

import { useReducer, useState } from "react";


import authReducer from "./reducers/authReducer";

const LoginStatus = () => {


// const [user, setUser] = useState('');
const [user, dispatch] = useReducer(authReducer, "");

if (user)
return (
<>
<div>
<span className="mx-2">{user}</span>
<a onClick={() => dispatch({ type: "LOGOUT" })} href="#">
Logout
</a>
</div>
</>
);
return (
<div>
<a
onClick={() => dispatch({ type: "LOGIN", username: "mosh.hamedani" })}
href="#"
>
Login
</a>
</div>
);
};

export default LoginStatus;

>>>>>>>>>>>> authReducer.ts

interface LoginAction {
type: 'LOGIN';
username: string;
}

interface LogoutAction {
type: 'LOGOUT'
}
type AuthAction = LoginAction | LogoutAction ;

const authReducer = (state: string, action: AuthAction): string => {


if(action.type === 'LOGIN') return action.username;
if(action.type === 'LOGOUT') return '';
return state;

}
export default authReducer;

*********** Lesson 43: Sharing state using React Context ***********


> Prop drilling in React refers to the practice of passing down props (properties)
through multiple layers of components in order to get data from a parent component
to a deeply nested child component that needs it.

> REACT CONTEXT: allows sharing data without passing it down through many
components in the middle.
> both the state and reducer hook are ways to maintain local state in a component
> lift the state up to the app component and then provide it to the components
using the context
> react context is like a truck which has a box to transport the state (first
define the shape of the box)
> Dispatch is a type that represents a function that takes a value of type A which
is a generic type parameter and returns void

STEPS:
1) lift the state up to the closest parent (here to app component)
2) create a context
3) wrap the component tree with the provider and the data you want to share
4) then you can acess the shared state using the context hooks in our components

>>>>>>>>>>>> app.tsx
import { useReducer } from "react";
import HomePage from "./state-management/HomePage";
import NavBar from "./state-management/NavBar";
import TasksContext from "./state-management/contexts/tasksContext";
import tasksReducer from "./state-management/reducers/tasksReducer";

function App() {
// now we have context, so we can share these two objects (tasks, dispatch) in
our component tree
const [tasks, dispatch] = useReducer(tasksReducer, []);

return (
// the value will replace the default value which is in tasksContext.ts
// this context is like a truck transporting this ({tasks, dispatch}) box ,,,
we can access this box anywhere in our component tree using the context hook
// we are sharing an object with 2 properties but we can share anything:
number, string, array, object and so on ,
<TasksContext.Provider value={{ tasks, dispatch }}>
<NavBar />
<HomePage />
</TasksContext.Provider>
);
}

export default App;

>>>>>>>>>>>>>> tasksReducer.ts
export interface Task {
id: number;
title: string;
}

interface AddTask {
type: 'ADD';
task: Task; // PayLoad
}
interface DeleteTask {
type: 'DELETE';
taskId: number; // PayLoad
}

// union of above two interfaces


export type TaskAction = AddTask | DeleteTask;

// tasks or state
const tasksReducer = (tasks: Task[], action: TaskAction ): Task[] => {
switch (action.type) {
case 'ADD':
return [action.task, ...tasks];
case 'DELETE':
return tasks.filter(t => t.id !== action.taskId);

export default tasksReducer;

>>>>>>>>>>>tasksContext.ts
import { Dispatch } from "react";
import { Task, TaskAction } from "../reducers/tasksReducer";
import React from "react";

//This is the object that we are going to transport using raectContext


interface TasksContextType {
tasks: Task[];
dispatch: Dispatch<TaskAction>;
}

const TasksContext = React.createContext<TasksContextType>({} as TasksContextType)

export default TasksContext;

// default value => ({} as TasksContextType)


// treat {} as an instance of TasksContextType

>>>>>>>>>>>>>>>> TaskList.tsx

import { useContext, useReducer, useState } from "react";


import tasksReducer from "./reducers/tasksReducer";
import TasksContext from "./contexts/tasksContext";

const TaskList = () => {


// const [tasks, setTasks] = useState<Task[]>([]);
// context is an object(or that box), (better to destructure it)
// we have shared tasks and dispatch function without prop drilling
const { tasks, dispatch } = useContext(TasksContext);
return (
<>
<button
onClick={() =>
dispatch({
type: "ADD",
task: { id: Date.now(), title: "Task" + Date.now() },
})
}
className="btn btn-primary my-3"
>
Add Task
</button>
<ul className="list-group">
{tasks.map((task) => (
<li
key={task.id}
className="list-group-item d-flex justify-content-between align-items-
center"
>
<span className="flex-grow-1">{task.title}</span>
<button
className="btn btn-outline-danger"
onClick={() => dispatch({ type: "DELETE", taskId: task.id })}
>
Delete
</button>
</li>
))}
</ul>
</>
);
};

export default TaskList;

********* Lesson 44: Exercise: Working with context *********

******* Lesson 45: Debugging with react dev tools *******


> How can you view the state of contexts using react dev tools
> you can use this tool to debug issues with react context

******* Lesson 46: Creating a custom Provider ********


> create a custom provider component in state management: AuthProvider.tsx

>>>>>>>>>>>>>>>>>>>>>>>> App.tsx

import { useReducer } from "react";


import HomePage from "./state-management/HomePage";
import NavBar from "./state-management/NavBar";
import TasksContext from "./state-management/contexts/tasksContext";
import tasksReducer from "./state-management/reducers/tasksReducer";
import authReducer from "./state-management/reducers/authReducer";
import AuthContext from "./state-management/contexts/authContext";
import AuthProvider from "./state-management/AuthProvider";

function App() {
// now we have context, so we can share these two objects (tasks, tasksDispatch)
in our component tree
const [tasks, tasksDispatch] = useReducer(tasksReducer, []);

return (
// the value will replace the default value which is in tasksContext.ts
// this context is like a truck transporting this ({tasks, tasksDispatch})
box ,,, we can access this box anywhere in our component tree using the context
hook
// we are sharing an object with 2 properties but we can share anything:
number, string, array, object and so on ,
<AuthProvider>
<TasksContext.Provider value={{ tasks, dispatch: tasksDispatch }}>
<NavBar />
<HomePage />
</TasksContext.Provider>
</AuthProvider>
);
}
export default App;

>>>>>>>>>>>AuthProvider.tsx
import { ReactNode, useReducer } from "react";
import authReducer from "./reducers/authReducer";
import AuthContext from "./contexts/authContext";

interface Props {
children: ReactNode;
}
// in this component, we are going to maintain the current user
const AuthProvider = ({ children }: Props) => {
const [user, dispatch] = useReducer(authReducer, "");

return (
<AuthContext.Provider value={{ user, dispatch }}>
{children}
</AuthContext.Provider>
);
};

export default AuthProvider;


// we should include the rest of our component tree in between
<AuthContext.Provider></AuthContext.Provider>
// we should be able to pass any component as a child to this component
// this component have both the state and the context provider

(no change in rest of the components)

*********** Lesson 47: Creating a (custom) Hook to access Context ***********


> just as we can create a custom provider , we can also create a custom hook for
accessing the given context
> create hooks folder in state management
then add a file useAuth.ts

>>>>>>>>>> LoginStatus.tsx
import useAuth from "./hooks/useAuth";

const LoginStatus = () => {


// const [user, setUser] = useState('');
const { user, dispatch } = useAuth(); // with this implelementation we donot have
to think about partivular context, we use hooks to get the shared objects
if (user)
return (
<>
<div>
<span className="mx-2">{user}</span>
<a onClick={() => dispatch({ type: "LOGOUT" })} href="#">
Logout
</a>
</div>
</>
);
return (
<div>
<a
onClick={() => dispatch({ type: "LOGIN", username: "mosh.hamedani" })}
href="#"
>
Login
</a>
</div>
);
};

export default LoginStatus;


>>>>>>>>>>>>>>>>>useAuth.ts
import { useContext } from "react";
import AuthContext from "../contexts/authContext";

const useAuth = () => useContext(AuthContext)


export default useAuth;

*********** lesson 48: EXERCISE : Creating a Provider ********

********* Lesson 49: Scalability and maintainability *********


> keep all the elements for working with tasks in a one place ,, by encapsulating
them inside a module or package we can easily import them and use them indifferent
parts of the application.
This makes our code modular, scalable and maintainable.
All the building blocks for working with tasks are inside a single folder (tasks)

> remote example:


we want to hide ceratin pieces that are about implementation and expose only the
parts that the consumer needs. In tasks folder, we only needs to expose two files
TasksList and TasksProvider(these are the components that we need to import in
other parts of the application). Everything else is considered implememntation
detail

> becz useTasks hook is not used by any other component except TaskList , so we can
delete that hook and move its implementation inside the TaskList component

> with ctrl + T, we can look up for any symbol inside our project

> with this structure,we are not exposing the implemntation detatils of the tasks
package and if we decide to change the implemtation in the future, we only need to
modify the code in the tasks folder. The changes will not impact the rest of the
application because the app donot care how we manage the state internally,

************ Lesson 50: Exercise: Organizing Code ************


In state-management folder, we have 3 functional areas. These functional areas are
complete;y independent of eachother. As our application grows complex, instead of
dividing our application into reducers, hooks or context, we should divide them
into functional areas
*********** LEsson 51 : Splitting Contexts for efficiency *********

> Anytime something in a context changes, all compoennts that use that context will
re render. A context should onlly hold values that are closely related and tend to
change together( A context should have a single purpose)

> Minimizing renders: Split up a context into smaller and focused ones each having
a single responsibilty

> react context: Super helpful for sharing data across react applications

*********** Lesson 52: When to use Context **********


>Server state : is the data that we fetch from the backend
Avoid react context for storing and sharing server states because you will end up
with two many contexts each holding a certain type of object. As in game hub ,we
will have many different contexts for shring genres, games, tags etc. USE react
query to manage server state.
React query is designed for this purpose and gives alot of control about how the
data is stored and cached

> Client state : is teh data that represents the state of client or UI (EXAMPLE:
current user, selected theme)

You might also like