- useState
- useState use cases
- State management
- Conditional rendering
- Toggle flags
- Counter
- Get API data and store it in state
- useReducer
- useReducer use cases
- Manage multiple states
- Modify complex states, such as arrays or objects: login form
- useEffect
- useEffect use cases
- Running once on mount: fetch API data
- Running on state change: validating input field
- Running on state change: live filtering
- Running on state change: trigger animation on new array value
- Running on props change: update paragraph list on fetched API data update
- Running on props change: updating fetched API data to get updated BTC price
- useMemo
- useMemo use cases
- Expensive function call: get a list of Reddits and sort them alphabetically
- Expensive function call: create a complex initial value counting how many posts have more than 10 votes in a subreddit
- useCallback
- useCallback use cases
- useRef
- useContext
useState
useState
is a Hook that needs to be called inside a function component to add some local state to it. React will preserve this state between component re-renders.
There are many use cases for the useState
hook, but in this article, I will focus on the following five:
useState
use cases
- State management
- Conditional rendering
- Toggle flags (true/false)
- Counter
- Get API data and store it in state
State management
Let's start with a warning: don't write code in this way, because it will create an infinite loop:
import { useState } from "react";
const UseCaseStateManagement = props => {
const [state, setState] = useState('initial value');
setState('new value');
console.log(state);
return (
<>
<h2>useState use case</h2>
<h3>State management</h3>
<hr />
<p>{state}</p>
</>
);
};
export default UseCaseStateManagement;
The loop is created because the initial render calls the state update function setState
, which in time triggers a re-render and a new function evaluation.
If we want to change a state due to an action performed by the user, we can do this:
import { useState } from "react";
const UseCaseStateManagement = props => {
const [state, setState] = useState('initial value');
console.log('🔄 This is a re-render');
const clickHandler = () => {
setState('new value');
};
return (
<>
<h2>useState use case</h2>
<h3>State management</h3>
<hr />
<button onClick={clickHandler}>Set state</button>
<p>{state}</p>
</>
);
};
export default UseCaseStateManagement;
That state will be preserved across component re-renders and we will be able to make use of it in the newest re-render.
Conditional rendering
We can use a state to conditionally render a component or part of it.
import { useState } from "react";
const UseCaseConditionalRender = props => {
const [condition, setCondition] = useState(false);
const clickHandler = () => {
setCondition(true);
};
return (
<>
<hr />
<h2>useState use case</h2>
<h3>Conditional Rendering</h3>
<button onClick={clickHandler}>Set condition</button>
{condition && <p>Hello!</p>}
</>
);
};
export default UseCaseConditionalRender;
Toggle flags
useState
can be used to toggle between two values, usually true
and false
, in order to toggle a flag, such as the display mode:
import { useState } from 'react';
import classes from './UseCaseToggle.module.css';
const UseCaseToggle = props => {
const [mode, setMode] = useState(false);
// Use setState function form because the new state depends on the previous one
const clickHandler = () => {
setMode(prevState => !prevState);
};
const toggledClass = mode ? classes.light : classes.dark;
return (
<div className={toggledClass}>
<hr />
<h2>useState use case</h2>
<h3>Toggle flags</h3>
<button onClick={clickHandler}>Toggle display mode</button>
</div>
);
};
export default UseCaseToggle;
The result will be an alternation between dark and light mode on the component.
Counter
useState
can be used to keep track of a variable through multiple re-renders, such as in a counter application:
import { useState } from "react";
const UseCaseCounter = props => {
const [counter, setCounter] = useState(0);
// Use setState function form because the new state depends on the previous one
const clickHandlerDecrease = () => {
// Converting the prevState to number to avoid errors
setCounter(prevState => +prevState - 1);
};
const clickHandlerIncrease = () => {
setCounter(prevState => +prevState + 1);
};
return (
<>
<hr />
<h2>useState use case</h2>
<h3>Counter</h3>
<button onClick={clickHandlerDecrease}>--</button>
<span> {counter} </span>
<button onClick={clickHandlerIncrease}>++</button>
</>
);
};
export default UseCaseCounter;
Get API data and store it in state
A more complex use of this hook is presented when we need to interact with an API. In this case, we can use a state to store the response of a fetch()
to the API, and the state of a spinner that will indicate if the data is being fetched.
import { useState } from "react";
const UseCaseApi = props => {
const [starship, setStarship] = useState('');
const [isLoading, setIsLoading] = useState(false);
const clickHandler = async () => {
setIsLoading(true);
const response = await fetch('https://swapi.dev/api/starships/10');
const data = await response.json();
setStarship(JSON.stringify(data, null, "\t"));
setIsLoading(false);
};
let message = '';
if (isLoading) {
message = <p>Getting data... 🚀</p>;
}
return (
<>
<hr />
<h2>useState use case</h2>
<h3>Get API data and store it in state</h3>
<button onClick={clickHandler}>Get Millennium Falcon data</button>
<p>{message}</p>
<pre>{starship}</pre>
</>
);
};
export default UseCaseApi;
You can watch all these examples live here: https://6142b64c2e448f0007271a3f--romantic-booth-593b0c.netlify.app/usestate/state-management
You can also take a look at the code in this repository: https://github.com/Colo-Codes/react-hooks-use-cases/tree/main/src/components/UseCaseUseState
useReducer
useReducer
is a Hook that allows us to manage multiple states more efficiently, create complex state logic, and manage states that depend on previous states. The following two use cases are good examples of how we can make use of this hook.
useReducer
use cases
- Manage multiple states: modify an array
- Modify complex states, such as arrays or objects: login form
Manage multiple states
useReducer
can be used to simplify the way in which multiple states impact a piece of data. In this case, adding, removing, and clearing an array can be achieved by using useReducer
instead of three separate states.
import { useReducer } from "react";
const myReducer = (prevState, action) => {
let array;
switch (action.type) {
case 'ADD':
array = [...prevState];
array.push(action.payload);
return array;
case 'REMOVE':
array = [...prevState];
array.pop();
return array;
case 'CLEAR':
return prevState = [];
default:
break;
}
};
const UseCaseMultipleStates = props => {
const [state, dispatcher] = useReducer(myReducer, ['initial value']);
console.log(state);
// Three different state triggers
const addHandler = () => {
dispatcher({ type: 'ADD', payload: Math.round((Math.random() * 100 + 100)) });
};
const removeHandler = () => {
dispatcher({ type: 'REMOVE' });
};
const clearHandler = () => {
dispatcher({ type: 'CLEAR' });
};
return (
<>
<hr />
<h2>useReducer use case</h2>
<h3>Manage multiple states: modify an array</h3>
<button onClick={addHandler}>[+] Add random value to array</button>
<button style={{ margin: "0 2rem" }} onClick={removeHandler}>[-] Remove last value from array</button>
<button onClick={clearHandler}>[x] Clear array</button>
<p>Shopping cart array:</p>
<p><b>{state.length === 0 && '(empty)'}{state.join(' - ')}</b></p>
</>
);
};
export default UseCaseMultipleStates;
Modify complex states, such as arrays or objects: login form
useReducer
can be especially handy when dealing with multiple states and a complex state logic.
By handling a login form with this hook instead of multiple useState
hooks we can appreciate how powerful this hook is.
Helper function for simulating a login API (thanks to Harry Wolff for this code):
export async function loginHelper({ username, password }) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (username === 'user' && password === 'password') {
resolve();
} else {
reject();
}
}, 1000);
});
}
Main component:
import { useReducer } from "react";
import { loginHelper } from "./loginHelper";
const myReducer = (prevState, action) => {
switch (action.type) {
case 'USERNAME':
return {
...prevState,
username: action.payload,
};
case 'PASSWORD':
return {
...prevState,
password: action.payload,
};
case 'LOGGED_IN':
return {
...prevState,
isLoggedIn: true,
};
case 'LOGGED_OUT':
return {
...prevState,
isLoggedIn: false,
username: '',
password: '',
};
case 'IS_LOADING':
return {
...prevState,
isLoading: true,
};
case 'IS_NOT_LOADING':
return {
...prevState,
isLoading: false,
};
case 'ERROR':
return {
...prevState,
isError: true,
isLoading: false,
};
default:
break;
}
};
const initialState = {
username: '',
password: '',
isLoggedIn: false,
isLoading: false,
isError: false,
};
const UseCaseComplexStates = props => {
const [state, dispatcher] = useReducer(myReducer, initialState);
const usernameHandler = e => {
dispatcher({ type: 'USERNAME', payload: e.target.value });
};
const passwordHandler = e => {
dispatcher({ type: 'PASSWORD', payload: e.target.value });
};
const logoutHandler = e => {
dispatcher({ type: 'LOGGED_OUT' });
};
const submitHandler = async e => {
e.preventDefault();
// Check credentials (simulated)
try {
dispatcher({ type: 'IS_LOADING' });
await loginHelper({ username: state.username, password: state.password });
dispatcher({ type: 'IS_NOT_LOADING' });
dispatcher({ type: 'LOGGED_IN' });
} catch {
dispatcher({ type: 'ERROR' });
alert('🚨 Incorrect username or password');
}
};
return (
<>
<hr />
<h2>useReducer use case</h2>
<h3>Modify complex states, such as arrays or objects: login form</h3>
<div style={{ maxWidth: '50%', backgroundColor: '#a8dadc', borderRadius: '1rem', padding: '2rem' }}>
{state.isLoggedIn
? <><p>Welcome!</p><button onClick={logoutHandler}>Log out!</button></>
: <form onSubmit={submitHandler}>
<div style={{ margin: '1rem 0' }}>
<label htmlFor="username">Username</label>
<input type="text" id="username" onChange={usernameHandler} value={state.username} style={{ margin: '0 1rem' }} placeholder='user' />
</div>
<div style={{ margin: '1rem 0' }}>
<label htmlFor="password">Password</label>
<input type="password" id="password" onChange={passwordHandler} value={state.password} style={{ margin: '0 1rem' }} placeholder='password' />
</div>
<div style={{ margin: '1rem 0' }}>
<button type="submit" disabled={state.isLoading}>{state.isLoading ? 'Logging you in...' : 'Log in'}</button>
</div>
</form>
}
</div>
</>
);
};
export default UseCaseComplexStates;
useEffect
Whenever we need to make use of side effects in our application, useEffect
is the way to go. This hook doesn't present much complications, except for non primitive data types, due to how JavaScript handles them.
According to the official documentation, effects run after every completed render, but you can choose to fire them only when certain values have changed. This hook uses an array of "dependencies": variables or states that useEffect
listen to for changes. When their values change, the main body of the useEffect
hook is executed.
The return
statement of this hook is used to clean methods that are already running, such as timers. The first time this hook is called, its main body is the one that is going to be evaluated first. All other subsequent times the hook is called, the return statement will be evaluated first, and, after that, the hook's main body. This behaviour is especially useful for cleaning code that is already running before run it again, which enable us to prevent memory leaks.
There is an interesting behaviour with this hook when we use non-primitive JavaScript data types as dependencies (e.g., arrays, objects, functions). With primitive values, such as numbers and strings, we can define a variable from another variable, and they will be the same:
const a = 1
const b = 1
a === b
// Output: true
But with non primitive values, such as objects, this behaviour is not the same:
{} === {}
// Output: false
So we need to be very careful when using objects as dependencies, because even though they may look as unaltered data, they may not be so. Instead of using objects, we may want to use their properties as dependencies:
useEffect(() => {
// Some code that uses the properties
}, [myObject.property1, myObject.property2]);
Now, let's take a look at some use cases for this hook.
useEffect
use cases
- Running once on mount: fetch API data
- Running on state change: validating input field
- Running on state change: live filtering
- Running on state change: trigger animation on new array value
- Running on props change: update paragraph list on fetched API data update
- Running on props change: updating fetched API data to get BTC updated price
Running once on mount: fetch API data
When we want to perform an action once, especially when the app loads or mounts, we can use useEffect
to do it. In this case, we are triggering a fetch()
GET request when the app is mounted, using an empty array as useEffect
dependency.
import { useState, useEffect } from "react";
const UseCaseFetchApi = props => {
// useState is needed in order to display the result on the screen
const [bio, setBio] = useState({});
// 'async' shouldn't be used in the useEffect callback function because these callbacks are synchronous to prevent race conditions. We need to put the async function inside.
useEffect(() => {
const fetchData = async () => {
const response = await fetch('https://swapi.dev/api/people/1/');
const data = await response.json();
console.log(data);
setBio(data);
};
fetchData();
}, []);
// Empty dependencies array will make useEffect to run only once at startup because that array never changes
return (
<>
<hr />
<h2>useEffect use case</h2>
<h3>Running once on mount: fetch API data</h3>
<p>Luke Skywalker's bio:</p>
<pre>{JSON.stringify(bio, null, '\t')}</pre>
</>
);
};
export default UseCaseFetchApi;
Running on state change: validating input field
Validating an input while it's receiving characters is another great application for useEffect
. Whilst the input is being stored in a state using useState
, the validation takes place every time the input changes, giving immediate feedback to the user.
We could add a setTimeout()
function to check the input field after some time, to delay the checking on each user keystroke, and we would need to clear that timer by using the clearTimeout()
function in the return statement of the useEffect
hook. A similar example of this is implemented in the useEffect
animation trigger, further ahead.
import { useEffect, useState } from "react";
const UseCaseInputValidation = props => {
const [input, setInput] = useState('');
const [isValid, setIsValid] = useState(false);
const inputHandler = e => {
setInput(e.target.value);
};
useEffect(() => {
if (input.length < 5 || /\d/.test(input)) {
setIsValid(false);
} else {
setIsValid(true);
}
}, [input]);
return (
<>
<hr />
<h2>useEffect use case</h2>
<h3>Running on state change: validating input field</h3>
<form>
<label htmlFor="input">Write something (more than 5 non numerical characters is a valid input)</label><br />
<input type="text" id="input" autoComplete="off" onChange={inputHandler} style={{ height: '1.5rem', width: '20rem', marginTop: '1rem' }} />
</form>
<p><span style={isValid ? { backgroundColor: 'lightgreen', padding: '.5rem' } : { backgroundColor: 'lightpink', padding: '.5rem' }}>{isValid ? 'Valid input' : 'Input not valid'}</span></p>
</>
);
};
export default UseCaseInputValidation;
Running on state change: live filtering
We can use useEffect
to filter an array "on the fly" by typing letters into an input element. To do so, we will need to use a state to save the input, and a filter implementation inside the useEffect
that will be triggered when the input changes, thanks to useEffect
dependencies.
import { useEffect, useState } from "react";
const array = [
{ key: '1', type: 'planet', value: 'Tatooine' },
{ key: '2', type: 'planet', value: 'Alderaan' },
{ key: '3', type: 'starship', value: 'Death Star' },
{ key: '4', type: 'starship', value: 'CR90 corvette' },
{ key: '5', type: 'starship', value: 'Star Destroyer' },
{ key: '6', type: 'person', value: 'Luke Skywalker' },
{ key: '7', type: 'person', value: 'Darth Vader' },
{ key: '8', type: 'person', value: 'Leia Organa' },
];
const UseCaseLiveFilter = props => {
const [inputValue, setInputValue] = useState('');
const [inputType, setInputType] = useState('');
const [filteredArray, setFilteredArray] = useState(array);
const inputValueHandler = e => {
setInputValue(e.target.value);
};
const inputTypeHandler = e => {
setInputType(e.target.value);
};
useEffect(() => {
setFilteredArray((_) => {
const newArray = array.filter(item => item.value.includes(inputValue)).filter(item => item.type.includes(inputType));
return newArray;
});
}, [inputValue, inputType]);
// Prepare array to be rendered
const listItems = filteredArray.map((item) =>
<>
<tr>
<td style={{ border: '1px solid lightgray', padding: '0 1rem' }}>{item.type}</td>
<td style={{ border: '1px solid lightgray', padding: '0 1rem' }} > {item.value}</td>
</tr >
</>
);
return (
<>
<hr />
<h2>useEffect use case</h2>
<h3>Running on state change: live filtering</h3>
<form style={{ maxWidth: '23rem', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<div>
<label htmlFor="input-type">Filter by <b>type</b></label><br />
<input type="text" id="input-type" autoComplete="off" onChange={inputTypeHandler} style={{ height: '1.5rem', width: '10rem', marginTop: '1rem' }} />
</div>
<div>
<label htmlFor="input-value">Filter by <b>value</b></label><br />
<input type="text" id="input-value" autoComplete="off" onChange={inputValueHandler} style={{ height: '1.5rem', width: '10rem', marginTop: '1rem' }} />
</div>
</form>
<br />
<table style={{ width: '20rem', border: '1px solid gray', padding: '0 1rem' }}>
<tr>
<th>Type</th>
<th>Value</th>
</tr>
{listItems}
</table>
</>
);
};
export default UseCaseLiveFilter;
Running on state change: trigger animation on new array value
We can use the useEffect
hook to trigger an animation on a shopping cart as a side effect of adding a new product to it. In this case, we'll need a state to handle the cart items, and another state to handle the animation trigger.
As we are using a timer inside the useEffect
, It is a good practice to clear it before it gets set again by using the return
statement of the useEffect
, which gets executed before the main body of the useEffect
hook gets evaluated (except for the first render).
import { useState, useEffect } from 'react';
import classes from './UseCaseAnimation.module.css';
const products = [
'Death Star',
'CR90 corvette',
'Millennium Falcon',
'X-wing fighter',
'TIE fighter'
];
const UseCaseAnimation = props => {
const [cart, setCart] = useState([]);
const [triggerAnimation, setTriggerAnimation] = useState(false);
// Add item to the cart (array)
const clickHandler = e => {
e.preventDefault();
setCart(prevCart => {
const newCart = [...prevCart];
newCart.push(e.target.value);
return newCart;
});
};
// Clear the cart (array)
const clearHandler = e => {
e.preventDefault();
setCart([]);
};
// Trigger cart animation
useEffect(() => {
setTriggerAnimation(true);
const timer = setTimeout(() => {
setTriggerAnimation(false);
}, 900); // The duration of the animation defined in the CSS file
// Clear the timer before setting a new one
return () => {
clearTimeout(timer);
};
}, [cart]);
const cartClasses = triggerAnimation ? `${classes['jello-horizontal']} ${classes.cart}` : classes.cart;
const itemsOnSale = products.map(itemOnSale => {
return <li><form><span className={classes.item}>{itemOnSale} <button onClick={clickHandler} value={`"${itemOnSale}"`}>Add to cart</button></span></form></li >;
});
const cartItems = cart.map(item => {
return <li>{item}</li>;
});
return (
<>
<hr />
<h2>useEffect use case</h2>
<h3>Running on state change: trigger animation on new array value</h3>
<h4 style={{ color: 'blue' }}>Starship Marketplace</h4>
<ul>
{itemsOnSale}
</ul>
<div className={cartClasses}><span>Cart</span></div>
<div>
<p>Elements in cart:</p>
<ul>
{cartItems}
</ul>
</div>
<form><button className={classes.margin} onClick={clearHandler} value="clear">Clear cart</button></form>
</>
);
};
export default UseCaseAnimation;
Running on props change: update paragraph list on fetched API data update
In this use case, we want to trigger a state update due to an updated fetch()
call. We are sending the fetched data to a child component, and whenever that data is changed, the child component re-process it.
import { useState, useEffect, useCallback } from "react";
const BaconParagraphs = props => {
const [baconParagraphText, setBaconParagraphText] = useState([]);
useEffect(() => {
setBaconParagraphText(props.chopBacon.map(piece => <p key={Math.random()}>{piece}</p>));
}, [props.chopBacon]); // Props
return (
<>
<p>Number of paragraphs: {baconParagraphText.length}</p>
{baconParagraphText}
</>
);
};
const UseCaseUpdateFetch = () => {
const [bacon, setBacon] = useState([]);
const [isLoading, setIsLoading] = useState(false);
const submitHandler = async e => {
e.preventDefault();
setIsLoading(true);
const response = await fetch(`https://baconipsum.com/api/?type=all-meat¶s=${e.target.paragraphs.value}&start-with-lorem=1`);
const data = await response.json();
setIsLoading(false);
setBacon(data);
};
return (
<>
<hr />
<h2>useEffect use case</h2>
<h3>Running on props change: update paragraph list on fetched API data update</h3>
<form onSubmit={submitHandler}>
<label htmlFor="paragraphs" style={{ display: "block", marginBottom: "1rem" }}>How many paragraphs of "Bacon ipsum" do you want?</label>
<select id="paragraphs" name="paragraphs">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
<option value="4">4</option>
</select>
<input type="submit" value="Show me the bacon!" style={{ marginLeft: "1rem" }} /> {isLoading && <span>Getting paragraphs... 🐷</span>}
</form>
<BaconParagraphs chopBacon={bacon} />
</>
);
};
export default UseCaseUpdateFetch;
Running on props change: updating fetched API data to get updated BTC price
In this example, useEffect
is used to fetch new data from an API every 3 seconds. The child component useEffect
receives the time as dependency, and every time that dependency changes, a new fetch()
is triggered. This way, we can have an updated BTC exchange rate in our app.
import { useState, useEffect } from "react";
import classes from './UseCaseUpdateApi.module.css';
// SECTION - Functions
const getCurrentTime = () => {
const now = new Date();
const time = now.getHours() + ':' + ('0' + now.getMinutes()).slice(-2) + ':' + ('0' + now.getSeconds()).slice(-2);
return time;
};
// SECTION - Components
const ExchangeRate = props => {
const [exchangeRate, setExchangeRate] = useState(0);
const [isAnimated, setIsAnimated] = useState(false);
useEffect(() => {
const getExchangeRate = async () => {
// Please don't abuse my personal API key :)
const response = await fetch("https://api.nomics.com/v1/exchange-rates?key=86983dc29fd051ced016bca55e301e620fcc51c4");
const data = await response.json();
console.log(data.find(item => item.currency === "BTC").rate);
setExchangeRate(data.find(item => item.currency === "BTC").rate);
};
getExchangeRate();
// Triggering animation
setIsAnimated(true);
const classTimer = setTimeout(() => {
setIsAnimated(false);
}, 1500);
// Clear the timer before setting a new one
return () => {
clearTimeout(classTimer);
setExchangeRate(exchangeRate); // Preventing Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.
};
}, [props.onTime]);
const priceClasses = isAnimated ? `${classes.price} ${classes.heartbeat}` : `${classes.price}`;
return <div className={priceClasses}>USD <b>{exchangeRate}</b></div>;
};
const UseCaseUpdateApi = props => {
const [time, setTime] = useState(getCurrentTime());
// Trigger the update interval on startup (mount)
useEffect(() => {
const interval = setInterval(() => {
setTime(getCurrentTime());
}, 3000);
return () => clearInterval(interval);
}, []); // Empty dependencies array, so it will run once at mount and keep running 'in the background'
console.log(time);
return (
<>
<hr />
<h2>useEffect use case</h2>
<h3>Running on props change: updating fetched API data to get updated BTC price</h3>
<span>Last updated: {time} (polling every 3 seconds)</span><ExchangeRate onTime={time} />
</>
);
};
export default UseCaseUpdateApi;
Finally, you can take a look at these use cases live here, and you can find the source code here.
useMemo
useMemo
is a Hook that implements memoization. But, what is memoization? According to Wikipedia, memoization or memoisation is an optimisation technique used primarily to speed up computer programs by storing the results of expensive function calls and returning the cached result when the same inputs occur again.
Whit the previous definition in mind, we can think of useMemo
as a means to manage optimisations that avoid having to perform expensive (resource intensive) calculations.
useMemo
has somewhat a similar intent than useCallback
, with the difference that useMemo avoids repeating expensive calculations, whilst useCallback
avoids recreating methods (functions) at every render. This question in Stack Overflow has further details on this difference.
useMemo
use cases
- Expensive function call: get a list of Reddits and sort them alphabetically whenever the list changes
- Expensive function call: create a complex initial value counting how many posts have more than 500 votes in a subreddit
Expensive function call: get a list of Reddits and sort them alphabetically
In this implementation of useMemo
an expensive function call is performed each time the user submits a new subreddit to be alphabetically sorted. We can see how, in spite of multiple renders, the calculation is performed only when the data coming from the API changes.
import { useState, useMemo } from "react";
const UseCaseSort = props => {
const [redditsList, setRedditsList] = useState([]);
const [isLoading, setIsLoading] = useState(false);
console.log('🖼 This is a render');
const submitHandler = async e => {
e.preventDefault();
setIsLoading(true);
const result = await fetch(`https://www.reddit.com/r/${e.target.input.value}.json`);
const myData = await result.json();
setIsLoading(false);
setRedditsList(myData.data.children);
};
const expensiveCalculation = (redditsList) => {
return redditsList.map(post => post.data.title).sort();
};
const listDisplay = useMemo(() => {
console.log('🔄 Change detected! Recalculating...');
const sortedTitlesList = expensiveCalculation(redditsList);
return sortedTitlesList.map(title => <li key={Math.random()}>{title}</li>);
}, [redditsList]);
return (
<>
<hr />
<h2>useMemo use case</h2>
<h3>Expensive function call: get a list of Reddits and sort them alphabetically</h3>
<form onSubmit={submitHandler}>
<label htmlFor="input">Enter a subreddit</label>
<input type="text" id="input" name="input" placeholder="reactjs" style={{ margin: '0 1rem' }} />
<input type="submit" value="Get posts!" />
</form>
{isLoading && <p>⏳ Loading data...</p>}
{redditsList.length > 0 && !isLoading ?
<>
<p>List of ReactJS subreddit posts alphabetically sorted:</p>
<ol>
{listDisplay}
</ol>
<p> <i>Take a look at the console ;)</i></p>
</>
: ''
}
</>
);
};
export default UseCaseSort;
Expensive function call: create a complex initial value counting how many posts have more than 10 votes in a subreddit
Using an empty dependency array in useMemo
will trigger the hook during the first rendering of the app.
import { useMemo, useState } from "react";
const UseCaseInitialValue = props => {
const [redditsList, setRedditsList] = useState([]);
const [isLoading, setIsLoading] = useState(false);
console.log('🖼 This is a render');
useMemo(async () => {
console.log('🔄 Running calculation...');
setIsLoading(true);
const result = await fetch(`https://www.reddit.com/r/reactjs.json`);
const myData = await result.json();
setIsLoading(false);
const topSorted = myData.data.children.filter(post => post.data.ups > 10).sort((a, b) => (a.data.ups < b.data.ups) ? 1 : -1).map(post => {
return <li key={post.data.id}>🗳 Votes: {post.data.ups - post.data.downs} - 📝 <a href={`https://www.reddit.com/${post.data.permalink}`} target="_blank" rel="noreferrer">{post.data.title}</a></li>;
});
setRedditsList(topSorted);
}, []); // Run at startup
return (
<>
<hr />
<h2>useMemo use case</h2>
<h3>Expensive function call: create a complex initial value counting how many posts have more than 10 votes in a subreddit</h3>
<p>Using "reactjs" as a subreddit. These are the posts with over 10 votes, sorted by vote counts in descending order:</p>
{isLoading ? <p>⏳ Loading...</p> : redditsList}
<p><i>Take a look at the console ;)</i></p>
</>
);
};
export default UseCaseInitialValue;
useCallback
useCallback
...
useCallback
use cases
useRef
useContext
useState with useEffect
useState with useRef