REACT-SMILGA-S06A-HOOKS (useEffect) & CONDITIONAL
RENDERING
1. POTENTIAL PROBLEM - EVERY RE-RENDER WILL TRIGGER FN INVOKATION
USEEFFECT HOOK
2. USING USEEFFECT TO ENSURE ITS C/B FN ONLY RUNS ON INITIAL RENDER
3. USEEFFECTs C/B FN CANT RETURN A PROMISE - BUT A FN INSIDE THE C/B
FN CAN
4. MULTIPLE USEEFFECTS IN 1 COMPONENT
5. PASSING MULTIPLE VALS INTO USEEFFECT DEPENDENCY ARRAY
6. USING USEEFFECT FOR FETCHING DATA
MULTIPLE RETURNS / CONDITIONAL RENDERING
1. FETCHING DATA IS THE MOST COMMON USE CASE FOR USEEFFECT
2. FETCHING DATA IS THE PERFECT USE CASE FOR MULTIPLE RETURNS
3. FETCH ERRORS GOTCHA
4. DON'T CALL HOOKS CONDITIONALLY
5. PLACE USEEFFECT BEFORE MULTIPLE RETURNS
6. SHORT-CIRCUIT OPERATORS
7. TERNARY OPERATORS
8. TOGGLE STATE FOR CONDITIONAL RENDERING
9. USEEFFECT REPEATING INITIAL RENDER OF TOGGLED COMPONENT EVEN
THO PASSED EMPTY DEPENDENCY ARR - USEEFFECT “CLEANUP” FN
(SETINTERVAL, CLEARINTERVAL OR REMOVE EVENT LISTENER) TO THE
RESCUE
10. YOU MIGHT NOT NEED TO USE USEEFFECT
11. PPL ARE STARTING TO USE A UTILS.JS FILE - INSTEAD OF USEEFFECT()
POTENTIAL PROBLEM - EVERY RE-RENDER WILL TRIGGER FN INVOKATION
*You may have a FN Call in your Component - a problem occurs when you UPDATE
the STATE - a Re-Render is Triggered & that will cause the FN to be Invoked
EVERY TIME - this can cause an Infinite Loop:
const [value, setValue] = useState(0);
const sayHello = () => {
console.log('hello there');
// This will cause an infinite loop (bc its Updating STATE (which Triggers Re-Render
& thus “sayHello();” Runs on every Re-Render
setValue(value + 1);
};
sayHello();
*”useEffect” Hook helps in this Situation
USEEFFECT HOOK
*”useEffect is a React Hook that lets you synchronize a component with an external
system.”
*It’s most common use case = FETCHING DATA
*Gets its Name from SIDE EFFECT
*Enables us to Run Code CONDITIONALLY & thus choose WHEN we want to Run
Certain Code in our Component
*useEffect is a hook in React that allows you to perform side effects in function
components.(basically any work outside of the component).
Some examples of side effects are: subscriptions, fetching data, directly updating
the DOM, event listeners, timers, etc.
*useEffect hook:
accepts two arguments (second optional)
first argument - cb function
second argument - dependency array
by default - it runs on each render (initial and re-render)
cb can't return promise (so can't make it async)
if dependency array empty [] runs only on initial render
USING USEEFFECT TO ENSURE ITS C/B FN ONLY RUNS ON INITIAL RENDER
*As useEffect Receices upto 2 Params (C/B FN & (optional) Dependency ARR) - if we
Pass it an Empty ARR (as the Dependency ARR) - the C/B = Only Run on the
Initial Render
useEffect(() => {
console.log('hello from useEffect');
}, []);
USEEFFECTs C/B FN CANT RETURN A PROMISE - BUT A FN INSIDE THE C/B FN
CAN
*So you can use ASYNC K/WORD In a FN INSIDE its C/B FN
MULTIPLE USEEFFECTS IN 1 COMPONENT
*You can do this but that doesnt mean you should - he covers this in more detail later
PASSING MULTIPLE VALS INTO USEEFFECT DEPENDENCY ARRAY
*We can pass multiple values to the dependency array and every time that
value is going to change, we will invoke the functionality inside of the callback
function again (as Opposed to it only being Run on Initial Render - if Passed an
Empty ARR)
Eg. In the 1st useEffect we have Passed a Reference to the Multiple STATE VALs
to the dependency array- so that UseEffect can Detect if there’s a Change to
those VALs & if so the C/B FN can be Run ea. Time there is a Change
const MultipleEffects = () => {
const [value, setValue] = useState(0);
const [secondValue, setSecondValue] = useState(0);
useEffect(() => {
console.log('hello from first useEffect');
}, [value, secondValue]);
useEffect(() => {
console.log('hello from second useEffect');
}, [secondValue]);
return (
<div>
<h1>value : {value}</h1>
<button className="btn" onClick={() => setValue(value + 1)}>
value
</button>
<h1>second value : {secondValue}</h1>
<button className="btn" onClick={() => setSecondValue(secondValue + 1)}>
second value
</button>
</div>
);
};
USING USEEFFECT FOR FETCHING DATA
*bc you can’t use useEffect Directly for ASYNC Tasks - you have to Put the ASYNC FN
INSIDE the useEffect’s C/B FN
*Once the Data is Fetched & Converted to an OBJ (.json()) - we then Call the STATEs
SET FN (“setUsers”) to Update the STATE VAL to now Contain the Fetched OBJ
import { useState, useEffect } from 'react';
const url = 'https://api.github.com/users';
const FetchData = () => {
const [users, setUsers] = useState([]);
useEffect(() => {
// you can also setup function outside
const fetchData = async () => {
try {
const response = await fetch(url);
const users = await response.json();
setUsers(users);
} catch (error) {
console.log(error);
}
};
fetchData();
}, []);
return (
<section>
…..
MULTIPLE RETURNS / CONDITIONAL RENDERING
*just like Vanilla JS, you can have Multiple Returns eg. a Return S/Ment in ea. of the IF
& ELSE Blocks & thus we can take adv. Of that to perform Conditional Rendering
*A Convention is to use “is” for Boolean VALs - eg. > if (isLoading)
*To demonstrate Multiple Returns & Conditional Rendering:
- we Initialise STATE VAL “isLoading” to TRUE
- bc it is TRUE Initially, the IF S/MENT will Render <h2>Loading.....</h2>
- BUT bc setTimeout was set beforehand (for 3 secs) & it Calls setIsLoading(false) - to
make the STATE VAL = False the Last return is Run after that (return <h2>Multiple
return basics</h2>)
const MultipleReturnsBasics = () => {
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
setTimeout(() => {
setIsLoading(false);
}, 3000);
}, []);
if (isLoading) {
return <h2>Loading.....</h2>;
}
return <h2>Multiple return basics</h2>;
};
FETCHING DATA IS THE MOST COMMON USE CASE FOR USEEFFECT
FETCHING DATA IS THE PERFECT USE CASE FOR MULTIPLE RETURNS
*bc there are 3 Possible Scenarios when Fetching Data
1. Loading State
2. Error State
3. Success State
- & then Render the Appropriate Content According to which State we are In
*In the 1st Scenario (IF “isLoading” = True) > return <h2>Loading...</h2>
*In the 2nd Scenario (IF “isError” = True) > return <h2>ERROR!...</h2>
import { useEffect, useState } from 'react';
const url = 'https://api.github.com/users/QuincyLarson';
const MultipleReturnsFetchData = () => {
const [isLoading, setIsLoading] = useState(true);
const [isError, setIsError] = useState(false);
const [user, setUser] = useState(null);
useEffect(() => {
const fetchURL = async () => {
try {
const response = await fetch(url);
// Check the "ok" PROP for its Status (True/False)
if (!response.ok) {
// Set States Accordingly
setIsError(true);
setIsLoading(false);
// Dont Run rest of TRY Block - bc not needed (bc = Error)
return;
}
const data = await response.json();
setUser(data);
} catch (error) {
setIsError(true);
}
// Change to False bc we have now finished Loading
setIsLoading(false);
};
fetchURL();
}, []);
if (isLoading) {
return <h2>Loading...</h2>;
}
if (isError) {
return <h2>ERROR!...</h2>;
}
return (
<div>
<img
style={{ width: '150px', borderRadius: '25%' }}
src={user.avatar_url}
alt={user.name}
/>
<h2>{user.name}</h2>
<h4>works at {user.company}</h4>
<p>{user.bio}</p>
</div>
);
};
export default MultipleReturnsFetchData;
FETCH ERRORS GOTCHA
*Unlike for example Axios, by default, the fetch() API does not consider HTTP
status codes in the 4xx or 5xx range to be errors. Instead, it considers these status
codes to be indicative of a successful request
*So if NOT using eg. AXIOS (& just using FETCH API) - check the “ok” PROP (of the
Fetched OBJ) Status (= T/F) - to Set the STATE VALs Accordingly:
// Check the "ok" PROP for its Status (True/False)
if (!response.ok) {
// Set States Accordingly
setIsError(true);
setIsLoading(false);
// Dont Run rest of TRY Block - bc not needed (bc = Error)
return;
}
DON'T CALL HOOKS CONDITIONALLY
*Eg. In an IF S/Ment
*Also, they must be called in the exact same order in every component render.
*This wont work (bc = Called Conditionally)
const Example = () => {
const [condition, setCondition] = useState(true);
if (condition) {
// won't work
const [state, setState] = useState(false);
}
PLACE USEEFFECT BEFORE MULTIPLE RETURNS
*eg. Don’t place it after an Early Return
SHORT-CIRCUIT OPERATORS
*bc we cant use IF Conditions inside of JSX - we need to use S/Circuiting
* OR “||” - useful for Setting a Default VAL (in the Case of eg. an Unsuccessful
Fetch)
* AND “&&” - useful for Rendering a Component (in the Case of eg. a Successful
Fetch)
*Wont work:
return (
<div>
{ if (someCond) { count++;} }
…..
return (
<div>
<h4>Falsy OR : {text || 'hello world'}</h4>
<h4>Falsy AND {text && 'hello world'}</h4>
<h4>Truthy OR {name || 'hello world'}</h4>
<h4>Truthy AND {name && 'hello world'}</h4>
{codeExample}
</div>
);
*Short-Circuiting is useful for eg. You try to Fetch Data & if:
A. You Don’t get a successful Fetch: > <h2>{text || 'default value'}</h2>
B. You get a successful Fetch: > {user && <SomeComponent name={user.name} />}
TERNARY OPERATORS
*eg. If User is Logged in = Display name - ELSE Display message to Login
{user ? (
<div>
<h4>hello there user {user.name}</h4>
</div>
):(
<div>
<h2>please login</h2>
</div>
TOGGLE STATE FOR CONDITIONAL RENDERING
*In this Ex - we used a BTN to Toggle the Boolean in the STATE
- If it was TRUE = Render an additional Component
const ToggleChallenge = () => {
// STATE
const [showAlert, setShowAlert] = useState(false);
// onCLK use the SET STATE FN to Toggle the Boolean in STATE
return (
<div>
// onCLK = Toggle STATE VAL (FN to be Run is In Anon. FN - Else Runs Immed.)
<button className="btn" onClick={() => setShowAlert(!showAlert)}>
toggle alert
</button>
{/* IF STATE Boolean = TRUE = Render Additional Component */}
{showAlert && <Alert />}
</div>
);
};
const Alert = () => {
return <div className="alert alert-danger">hello world</div>;
CONDITIONAL RENDERING - LOGIN/OUT EX.
*A. If “user” (STATE VAL) = TRUE (NOT its Default “null”) - Render:
1. The Users Name 2. Logout <btn>
*B. If “user” (STATE VAL) = NULL (NOT = TRUE) - Render:
1. Please Login Msg 2. Login <btn>
C. When onClick Login BTN - call “Login” FN to SET STATE - Passes OBJ (w/ “name”)
- to STATE
D. When onClick Logout BTN - call “Logout” FN to SET STATE - to “null”
const UserChallenge = () => {
// State
const [user, setUser] = useState(null);
// Login FN
const login = () => {
// Normally = Connect to a DB or API
setUser({ name: 'soy boy' });
};
// Logout FN
const logout = () => {
// Normally = Connect to a DB or API
setUser(null);
};
return (
<div>
{/* IF user = True */}
{user ? (
<div>
<h4>Hi, {user.name}</h4>
<button className="btn" onClick={logout}>
logout
</button>
</div>
):(
<div>
<h4>please login</h4>
<button className="btn" onClick={login}>
login
</button>
</div>
)}
</div>
);
};
USEEFFECT REPEATING INITIAL RENDER OF TOGGLED COMPONENT EVEN
THO PASSED EMPTY DEPENDENCY ARR - USEEFFECT “CLEANUP” FN
(SETINTERVAL, CLEARINTERVAL OR REMOVE EVENT LISTENER) TO THE
RESCUE
*Even though we Had an Empty ARR being Passed as the Dependency ARR to
“useEffect” - (which means that the c.log should have only Displayed on Initial PG Load)
bc the Component is being Rendered Conditionally (thru Toggling T/F in STATE
VAL), when we start toggling the component, essentially we mount and unmount
the component, and during the mount, we repeat initial render.
*We need to be careful when we Toggle Components bc if we have some sort of
Functionality that Runs upon Toggle it could keep Running on ea. Toggle when its
not supposed to
*In order to Address the Above issue, use the “useEffect Cleanup FN” (puts a
“setInterval()” - w/ a “clearInterval()” INSIDE USEEFFECT)
const CleanupFunction = () => {
const [toggle, setToggle] = useState(false);
return (
<div>
<button className="btn" onClick={() => setToggle(!toggle)}>
toggle component
</button>
{toggle && <RandomComponent />}
</div>
);
};
const RandomComponent = () => {
useEffect(() => {
// console.log('hmm, this is interesting');
const intID = setInterval(() => {
console.log('hello from interval');
}, 1000);
clearInterval(intID);
}, []);
return <h1>hello there</h1>;
};
/////////// OR - IN THE CASE OF EVT/L’s /////////////
*Inside useEffect - Return “removeEventListener”
useEffect(() => {
// console.log('hmm, this is interesting');
const someFunc = () => {
// some logic here
};
window.addEventListener('scroll', someFunc);
return () => window.removeEventListener('scroll', someFunc);
}, []);
YOU MIGHT NOT NEED TO USE USEEFFECT
*A lot of Devs overused it & BLoated the Code - so React Community is Advising us to
use alternatives
*There are plenty of Alternatives now
*But there is still a lot of Code using it still
*Fetching data replaced by libraries - react query, rtk query, swr or next.js
*This URL is a good guide & gives you Alternative JS Code instead of useEffect in a lot
of Scenarios: https://react.dev/learn/you-might-not-need-an-effect
- A lot of the time you can replace it w/ a FN,etc
*”REACT QUERY” is 1 such library that can Fetch Data in 1 line of code - BUT you
wouldn’t use it on a small project w/ only a few GET Requests
PPL ARE STARTING TO USE A UTILS.JS FILE - INSTEAD OF USEEFFECT()
*And putting a FN in here & Exporting it
*utils.js
//// ppl are starting to use utils Instead of useEffect() (bc of overuse of it)
export const getTotals = (cart) => {
let totalAmount = 0;
let totalCost = 0;
// Calc Total amount of Items & Total Cost
// .values() - Returns the OBJ (which is the Values) From the MAP
for (let { amount, price } of cart.values()) {
totalAmount += amount;
totalCost += amount * price;
// console.log(item);
}
return { totalAmount, totalCost };
};