React Notes1
React Notes1
React Notes1
https://www.codewithc.com/database-management-system-project-c-wxwidgets/
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.
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.
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.
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 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.
BY HITESH CHAUDHRY:
* 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.
****** lecture 7
* 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?
function ListGroup() {
let items = ["New York", "San Francisco", "London", "Paris"];
// 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;
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>
</>
);
}
***App.tsx
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>
);
}
********ListGroup.tsx
// 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>
</>
);
}
function App() {
return (
<div className="alert alert-warning">
<Alert>Hello <i>World</i>
</Alert>
</div>
);
}
function App() {
return (
<div>
<Button color="dark" onClick={()=> console.log("CLICKED")}>My button</Button>
</div>
);
}
***** 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;
}
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>
);
}
interface Props {
items: string[];
heading: string;
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
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)
*** App.tsx
function App(){
return(
<div>
*** MY approach
* app.tsx
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;
}
* Button.css
.bg{
background-color: aqua;
font-style: italic;
}
.col{
color: blue;
}
function App() {
return (
<div>
<Button color="primary" onClick={()=> console.log("CLICKED")}>My
button</Button>
</div>
);
}
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;
}
* 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:
function App() {
return (
<div>
<Like onClick={() => console.log("clicked")} />
</div>
);
}
*Like.tsx
interface Props {
onClick: () => void;
}
EXAMPLE HERE::::
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>
);
}
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.
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>
);
}
// 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
// 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>
);
}
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
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>;
}
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
pure function : give the same input , always returns the same result
square program example
Message.tsx
let count = 0;
**** TWO
*** THREE
import Message from "./components/Message";
function App(){
return(
<div>
<Message/>
<Message/>
<Message/>
</div>
)
}
export default App
// 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
<Message> Message 1
Message 2
.. ... 3
... 4
.. ..5
.. 6
</div>
)
}
export default App
Message.tsx
let count = 0;
** 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
// 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);
}
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
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;
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;
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>
);
};
****************** Create a simple To-Do List application using React. The app
should have the following features:
function App() {
const [tags, setTags] = useState(["happy", "cheerful"]);
// 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
function App() {
const [bugs, setBugs] = useState([
{ id: 1, title: "Bug 1", fixed: false },
{ id: 2, title: "Bug 2", fixed: false },
]);
function App() {
const [bugs, setBugs] = useState([
{ id: 1, title: "Bug 1", fixed: false },
{ id: 2, title: "Bug 2", fixed: false },
]);
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>;
}
*** 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
interface Props {
cartItems: string[];
onClear: () => void;
}
**** Exercise 1:
import { useState } from "react";
function App() {
const [game, setGame] = useState({
id: 1,
player: {
name: "John",
},
});
function App() {
const [pizza, setPizza] = useState({
name: "Spicy pepproni",
toppings: ["Mushroom", "Olives"],
});
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>
);
}
**** 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>
);
}
ExpandableText.tsx
interface Props {
children: string;
maxChars: number;
}
if (!status)
return (
<div>
<>
<p>{children.slice(0, maxChars)} ....</p>
<button onClick={handleClick}>Read more</button>
</>
</div>
);
else return;
};
App.tsx
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>
);
}
*** ExpandableText.tsx
import React, { useState } from "react";
interface Props {
children: string;
maxChars?: number;
}
const ExpandableText = ({ children, maxChars = 100 }: Props) => {
const [isExpanded, setExpanded] = useState(false);
return (
<p>
{text}...
<button onClick={() => setExpanded(!isExpanded)}>
{isExpanded ? "Less" : "More"}
</button>
</p>
);
};
// 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
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>
);
};
// 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
// 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
<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>
);
};
//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.
// if you observe performance issues then use ref HOOK otherwise use useState
********
Lesson 52 : managing forms with react hook form ********
<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>
);
};
// react hook forms use reference objects (ref) to get values from the input field
so there is no rerendering involve here .....
interface FormData {
name: string;
age: number;
}// to define the shape of this form
<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>
);
};
// react hook forms use reference objects (ref) to get values from the input field
so there is no rerendering involve here .....
********* App.tsx
import Form from "./components/Form";
function App() {
return (
<div>
<Form/>
</div>
);
}
********** 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
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>
);
};
//npm i [email protected]
******** Lecture 55 *** disabling the submit button (if the form is invalid )
*********
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
app.tsx:::::::************
function App() {
//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>
);
}
/* 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
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
*/
********* APP.tsx
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.tsx
import React, { useEffect, useState } from "react";
// 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[]>([]);
// 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>;
};
****** APP.tsx
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>;
}
***** app.tsx
// 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[]>([]);
return <ul>
{users.map(user => <li key = {user.id}>{user.name}</li>)}
</ul>;
}
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
*/
// 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[]>([]);
return (
<ul>
<p>rendered</p>
{users.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
interface User {
id: number;
name: string;
}
function App() {
const [users, setUsers] = useState<User[]>([]);
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>
</>
);
}
interface User {
id: number;
name: string;
}
function App() {
const [users, setUsers] = useState<User[]>([]);
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>
</>
);
}
/*
- 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[]>([]);
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 (
<>
{error && <p className="text-danger">{error}</p>}
<ul>
{users.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
</>
);
}
/*
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
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();
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 (
<>
{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>
</>
);
}
/*
- 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
*/
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();
}, []);
// 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>
</>
);
}
// d-flex =====> by applying this, we convert each list item to a flex container
/*
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)
*/
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 (
<>
<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>
</>
);
}
- 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.
*/
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();
}, []);
axios
.delete("https://jsonplaceholder.typicode.com/users/" + user.id)
.catch((err) => {
setError(err.message);
setUsers(originalUsers);
});
};
axios
.post("https://jsonplaceholder.typicode.com/users", newUser)
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>
</>
);
}
***** api-client.ts*****
})
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);
});
apiClient
.post("/users", newUser)
***** api-client.ts****
})
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()
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);
});
userService.deleteUser(user.id).catch((err) => {
setError(err.message);
setUsers(originalUsers);
});
};
userService
.addUser(newUser)
.then(({ data: savedUser }) => {
setUsers([savedUser, ...users]);
})
.catch((err) => {
setError(err.message);
setUsers(originalUsers);
});
};
// our component should only focus on returning some markups and handling user
interactions
// this is the only place where we will provide an endpoint,,,,,, thiswill create
an instance of the class http-service
export default create('/users');
/*
GENERIC
a servicing class for managing posts
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)
}
add<T>(entity: T){
return apiClient.post(this.endpoint, entity)
}
//export a function for creating an instance of this class
const create = (endpoint: string) => new HttpService (endpoint)
export default create
// <T extends Entity> ===> objects of type T should extends the Entity interface
(meaning they should have an id property)
})
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);
});
userService.delete(user.id).catch((err) => {
setError(err.message);
setUsers(originalUsers);
});
};
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>
</>
);
}
useEffect(() => {
setLoading(true);
setError(err.message);
setLoading(false);
});
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();
userService.delete(user.id).catch((err) => {
setError(err.message);
setUsers(originalUsers);
});
};
userService
.add(newUser)
.then(({ data: savedUser }) => {
setUsers([savedUser, ...users]);
})
.catch((err) => {
setError(err.message);
setUsers(originalUsers);
});
};
/*
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
*/
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
*** 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
**** APP.tsx
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
}}>
</Grid>
}
to wrap:
first select the code
shift+ CTRL+ P
// we should pack related variables inside an object. So, here we will use query
object pattern
******* Lesson 103: Building sort selector *********
****** Lesson 106: Fixing the issue with Chakra Menus ******
> 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
* 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
(the more you code and experiment with what you have built, you become more skilled
and confident.
* 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)
<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>
interface Todo {
id: number;
title: string;
userId: number;
completed: boolean;
}
> 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)
*/
interface Todo {
id: number;
title: string;
userId: number;
completed: boolean;
}
return (
<ul className="list-group">
{todos?.map((todo) => (
<li key={todo.id} className="list-group-item">
{todo.title}
</li>
))}
</ul>
);
};
/*
> in axios, all errors are instances of Error interface that is available in all
browsers
interface Todo {
id: number;
title: string;
userId: number;
completed: boolean;
}
return (
<ul className="list-group">
{todos?.map((todo) => (
<li key={todo.id} className="list-group-item">
{todo.title}
</li>
))}
</ul>
);
};
/*
> with react query, we no longer have to declare separate state variables for
data , error, isLoading
*/
> 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
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
> 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
});
}
*/
>>>>> PostList.tsx
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>
</>
);
};
>>>>> usePosts.ts
import { useQuery } from "@tanstack/react-query"
import axios from "axios";
interface Post {
id: number;
title: string;
body: string;
userId: number;
}
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
*/
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>
</>
);
};
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;
}
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
*/
>>>>>>>>>> PostList.tsx
import { useState } from "react";
import usePosts from "./hooks/usePosts";
import React from "react";
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>
</>
);
};
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
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
*/
>>>>>>>>>>>>>>>>> TodoForm.tsx
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { useRef } from "react";
import { Todo } from "./hooks/useTodos";
import axios from "axios";
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>
);
};
return (
<>
{addTodo.error && (
<div className="alert alert-danger">{addTodo.error.message}</div>
)}
<form
className="row mb-3"
onSubmit={(event) => {
event.preventDefault();
return (
<>
{addTodo.error && (
<div className="alert alert-danger">{addTodo.error.message}</div>
)}
<form
className="row mb-3"
onSubmit={(event) => {
event.preventDefault();
>>>>>>>>>>>>>>> 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[];
}
return (
<>
{addTodo.error && (
<div className="alert alert-danger">{addTodo.error.message}</div>
)}
<form
className="row mb-3"
onSubmit={(event) => {
event.preventDefault();
// 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
>>>>>>>>>> TodoForm.tsx
import { useRef } from "react";
import useAddTodo from "./hooks/useAddTodo";
return (
<>
{addTodo.error && (
<div className="alert alert-danger">{addTodo.error.message}</div>
)}
<form
className="row mb-3"
onSubmit={(event) => {
event.preventDefault();
>>>>>>>>>>>>>>>>>>> useAddTodos.ts
>>>>>>>>> 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();
>>>>>>>>>>> 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";
>>>>>>>>>>>>>>>>>> useTodos.ts
import { useQuery } from "@tanstack/react-query";
import APIClient from "../services/apiClient";
import { CACHE_KEY_TODOS } from "../constants";
>>>>>>>>>>> apiClient.ts
import axios from "axios";
const axiosInstance = axios.create({
baseURL: 'https://jsonplaceholder.typicode.com'
})
constructor(endpoint: string) {
this.endpoint = endpoint;
}
}
export default APIClient;// export APIClient class
(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
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
" 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
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>
);
};
>>>>>>>>>>>>>>>>>>>>>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
}
>>>>>> TaskList.tsx
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>
</>
);
};
>>>>>>>>>>>>>>>>>> tasksReducer.ts
interface Task {
id: number;
title: string;
}
interface AddTask {
type: 'ADD';
task: Task; // PayLoad
}
interface DeleteTask {
type: 'DELETE';
taskId: number; // PayLoad
}
// 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);
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>
);
};
>>>>>>>>>>>> authReducer.ts
interface LoginAction {
type: 'LOGIN';
username: string;
}
interface LogoutAction {
type: 'LOGOUT'
}
type AuthAction = LoginAction | LogoutAction ;
}
export default authReducer;
> 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>
);
}
>>>>>>>>>>>>>> tasksReducer.ts
export interface Task {
id: number;
title: string;
}
interface AddTask {
type: 'ADD';
task: Task; // PayLoad
}
interface DeleteTask {
type: 'DELETE';
taskId: number; // PayLoad
}
// 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);
>>>>>>>>>>>tasksContext.ts
import { Dispatch } from "react";
import { Task, TaskAction } from "../reducers/tasksReducer";
import React from "react";
>>>>>>>>>>>>>>>> TaskList.tsx
>>>>>>>>>>>>>>>>>>>>>>>> App.tsx
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>
);
};
>>>>>>>>>> LoginStatus.tsx
import useAuth from "./hooks/useAuth";
> 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,
> 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
> Client state : is teh data that represents the state of client or UI (EXAMPLE:
current user, selected theme)