React Hooks by Example - Part 2
Hrittik Bhattacharjee
Posted on February 11, 2023
Learn (more) Hooks = Make (more) Money!🤑
We're back to make more Dinero!💵💵💵
Let's explore some of the less commonly used, but nonetheless super-important hooks in react!
The code for all the examples can be found at this Code Sandbox.
This is Part 2 of a three-part series, and it covers useReducer, useRef, useMemo, useCallback, useImperativeHandle and useLayoutEffect!
useReducer
- The
useReducer
can be used as an alternative touseState
. -
useReducer
is usually preferable touseState
when you have complex state logic that involves multiple sub-values or when the next state depends on the previous one." -
Example:
// /src/UseReducerExample.js import { useReducer } from "react"; import { Link } from "react-router-dom"; const bankBalance = { counter: 0 }; function reducer(state, action) { switch (action.type) { case "make 1 $": return { counter: state.counter + 1 }; case "spend 1 $": return { counter: state.counter - 1 }; default: throw new Error(); } } export default function UseReducerExample() { const [state, dispatch] = useReducer(reducer, bankBalance); return ( <> <h1> <Link to={"/"}> Dinero! </Link> </h1> <h2> {state.counter >= 0 ? "You have" : "You lost"} {Math.abs(state.counter)}{" "} 💵 </h2> <button onClick={() => dispatch({ type: "spend 1 $" })}>Spend 1 $</button> <button onClick={() => dispatch({ type: "make 1 $" })}>Make 1 $</button> </> ); }
-
In the above example:
- The
useReducer
hook accepts areducer
function and an data which you want to save to state, here, { counter: 0 }. - The
useReducer
hook returns astate
object containing this data, and adispatch
function. - When
dispatch
is called with anaction
here, { type: "spend 1 $" }, - It passes the
state
and thisaction
to thereducer
function. - Depending on the
action
type, it will return some value and this value will be saved to thestate
here, state.counter - The updated value of the state can then be accessed inside the component.
- The
useRef
- The
useRef
hook can be used to reference a given value, and it will be stored in its.current
property between component re-renders, and can be changed. - A common use-case is to access DOM elements directly.
- It is important to note that mutating
.current
will not cause a re-render. -
Example:
// /src/UseRefExample.js import { useRef } from "react"; import { Link } from "react-router-dom"; export default function UseRefExample() { let counter = 0; const refAmt = useRef(null); const updateAmt = () => { console.log(refAmt.current); counter += 1; console.log(`You actually have ${counter} 💵`); }; return ( <> <h1> <Link to={"/"}> Dinero! </Link> </h1> <h2 ref={refAmt}>You have {counter} 💵</h2> <button onClick={updateAmt}>Get 1 $</button> </> ); }
-
In the above example:
- We create a ref,
refAmt
. - in the
<h2>
element we specifyref={refAmt}
, so nowrefAmt.current
points to the<h2>
element. - On clicking the button, we can open the console and see that,
- The value of
counter
is increasing. - But
refAmt.current
(which points to the<h2>
element) is not being updated with the new value ofcounter
.
- We create a ref,
useMemo
- the
useMemo
hook takes a function (that ideally returns something) and an array of dependencies. - It will only re-compute the return value if one or more of the dependencies has changed.
- This helps to optimizae expensive computations on each render, that may not necessarily need to be re-computed.
- If no array is provided, a new value will be computed on every render.
- Every value referenced inside the function should also appear in the dependencies array.
- Side effects should be in
useEffect
, notuseMemo
. -
Example:
// /src/UseMemoExample.js import { useState, useMemo } from "react"; import { Link } from "react-router-dom"; function calculationRes(constant) { const result = constant ** 2; console.log(`The calculated result is: ${result}`); return result; } export default function UseMemoExample() { const [counter, setCounter] = useState(0); const [constant] = useState(1000000); const calculation = useMemo(() => calculationRes(constant), [constant]); return ( <> <h1> <Link to={"/"}> Dinero! </Link> </h1> <h2>You have {counter} 💵</h2> <button onClick={() => setCounter(counter + 1)}>Get 1 $</button> {calculation} </> ); }
-
In the above example:
- The function
calculationRes
runs once, the result is memoized andresult
is logged in console. - On clicking on the button, the state
counter
changes and hence the component re-renders. - But on subsequent renders,
result
is not logged to console. - This is because the value of
result
has not changed since the value of the variableconstant
has not changed, which was in the dependency array forcalculationRes
.
- The function
useCallback
- The
useCallback
hook is very similar to theuseMemo
hook, the only difference being thatuseMemo
returns a memoized value, anduseCallback
returns a memoized function. - A common use-case is when the function needs to be passed down nested child components.
-
The example above can be referred to, as an example of
useCallback
by just replacing:
const calculation = useMemo(() => calculationRes(constant), [constant]);
with
const calculation = useCallback(() => calculationRes(constant), [constant]);
forwardRef and useImperativeHandle
- As we have seen above, the
useRef
hook can be used to access DOM elements. - We can wrap the component exposing this
ref
, insideforwardRef
and then the parent component would have access to this ref. -
Example of forwarding refs:
import { useRef, forwardRef } from "react"; function ChildComponent(props, ref) { return ( <> <h1 ref={ref} {...props}>{"💵💵💵💵💵💵💵💵💵💵"}</h1> </> ); } ChildComponent = forwardRef(ChildComponent); export default function UseImperativeHandleExample() { const moneyRef = useRef(null); return ( <> <ChildComponent ref={moneyRef} /> </> ); }
-
In the above example:
- The parent component
UseImperativeHandleExample
uses theuseRef
hook to create a ref object. - This ref is pointed to
ChildComponent
. -
ChildComponent
is wrapped insideforwardRef
and hence we can assign this ref object to a DOM element inside it, here, the<h1> element
. - Now, the parent component
UseImperativeHandleExample
will have access to the ref to the<h1> element
insideChildComponent
.
- The parent component
Now, if we need to modify the behavious of this ref, based on say, some user action, this is where the
useImperativeHandle
hook comes in handy!-
The above example can be modified like so:
// /src/UseImperativeHandleExample.js import { useRef, forwardRef, useImperativeHandle } from "react"; import { Link } from "react-router-dom"; function ChildComponent(props, ref) { const h1Ref = useRef(null); useImperativeHandle(ref, () => ({ seeReferencedEl: h1Ref.current })); return ( <> <h1> <Link to={"/"}> Dinero! </Link> </h1> <h1 ref={h1Ref} {...props}> {"💵💵💵💵💵💵💵💵💵💵"} </h1> </> ); } ChildComponent = forwardRef(ChildComponent); export default function UseImperativeHandleExample() { const moneyRef = useRef(null); const seeReferencedElement = () => { console.log(moneyRef.current?.seeReferencedEl); }; return ( <> <ChildComponent ref={moneyRef} /> <button onClick={seeReferencedElement}>See ref in console</button> </> ); }
-
In the above example:
- Inside the
UseImperativeHandleExample
component,moneyRef.current
points toChildComponent
as we saw in the forwardRef example previously. - But in this example, we assign a new ref object
h1Ref
to the<h1> element
insideChildComponent
. - Using the
useImperativeHandle
hook, we assign this newh1Ref
to theseeReferencedEl
property ofChildComponent
's ref. - So now, inside the
UseImperativeHandleExample
component,moneyRef.current.seeReferencedEl
points to the<h1> element
insideChildComponent
.
- Inside the
useLayoutEffect
- The
useLayoutEffect
is similar touseEffect
, except one key difference. - It runs (synchronously) after the elements have been loaded into the DOM but before they are painted to the UI.
- "Use this [hook] to read layout from the DOM and synchronously re-render".
- A common use-case is to measure layout and change the position of elements based on it.
-
Example:
// /src/UseLayoutEffectExample.js import { useRef, useEffect, useLayoutEffect } from "react"; import { Link } from "react-router-dom"; export default function UseLayoutEffectExample() { const refAmt = useRef(null); useEffect(() => { console.log("useEffect called!"); }, []); useLayoutEffect(() => { console.log("useLayoutEffect called"); }, []); return ( <> <h1> <Link to={"/"} style={styles.linkStyle}> Dinero! </Link> </h1> <h2 ref={refAmt}>You have 1000000 💵</h2> </> ); }
-
In the above example:
- We see that
"useLayoutEffect called"
is logged first, since theuseLayoutEffect
runs synchronously - Then the DOM is visually updated.
- Then
"useEffect called!"
is logged, sinceuseEffect
runs asynchronously.
- We see that
Hope you had great fun learning hooks, and heading to the Buggati store soon for some Sunday shopping!
Stay tuned for Part 3 where we look at more hooks🤘
Cheers!
Posted on February 11, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.