Show More component
🌱 This post is in the growth phase. It may still be useful as it grows up.
This totorial is a complete guide to basic react hooks.
It demonstratens simple implementations of useState
, useEffect
, useRef
, useContext
, and useId
in a familiar, real-world component.
Contents
- Online sandboxes
- Create a component that renders children and a “Show less” button
- Conditionally render toggle text using a ternary operator
- Manage
expanded
state with state with theReact.useState
hook - Add an
onClick
event handler to the button - Call
React.useState
’s update function with new state - Style the content container with a
style
prop - Conditionally style content container based on state
- Access content container’s
scrollHeight
withReact.useRef
- Set
scrollHeight
of DOM node on button click - Set
maxHeight
of content container withcontentHeight
state value - Use
React.useEffect
to setcontentHeight
- Hide button if below height threshhold
- Update when window resizes, with
useEffect
- Spot the memory leak with
console.log
- Refactor the resize
useEffect
to call a function by reference - Add
useEffect
cleanup function - Debounce resize event handler
- Extract custom
useEffect
hook - Create Context with
React.createContext
- Provide Context with the
Context.Provider
component - Consume Context with
useContext
- Provide value and set function on Context
- Repeat for any required Contexts
Online sandboxes
- CodeSandbox Used by me (Vim support)
- StackBlitz
Make your own
Copy-paste this into your prefered React environment.
Does not include bootstrapping with React.creatRoot
.
Create a component that renders children and a “Show less” button
Let’s start.
Create a Component that renders children
and a “Show less” button
(If you’re sure what this code does, visit my React Basics tutorial for a primer.)
Reference: React Basics, chan.dev.
Conditionally render toggle text using a ternary operator
The “Show more” button should read “Show less” when expanded. Add a condition around the button text so that it can be toggled.
(Don’t worry about state. Just activate it, manually, with true
and false
.)
Reference: Conditional (ternary) Operator, MDN.
Manage expanded
state with state with the React.useState
hook
The React.useState
hook is how we manage UI state in React.
Create a state, using a initial value. Then assign that state to a local variable by destructuring the returned array.
Reference:
- Destructuring Assignment, MDN.
- useState hook reference, react.dev.
Add an onClick
event handler to the button
UI changes are activated by user input.
Add an onClick
event handler to the button and log the expanded
state.
Reference: Responding to Events, React Docs.
Call React.useState
’s update function with new state
The second value that we get from React.useState
is a state set function.
Assign that function to a local variable by destructuring it from the returned array. Then call the set function in the button’s onClick
event handler.
(To toggle the next state, invert the current state with !
operator.)
Reference: useState set functions referenc, react.dev
Style the content container with a style
prop
Styles rules can be applied directly to elements using the style
prop.
Style the collapsed state of the ShowMore component to be 100px
tall and hide overflowing content.
(We want to animate this. So use maxHeight
and set a transition
.)
Reference: Applying CSS style, react.dev.
Conditionally style content container based on state
Style rules can be set conditionally using the ternary operator.
Use a ternary to switch the maxHeight
value from 100px
to "none"
when expanded
.
Access content container’s scrollHeight
with React.useRef
React.useRef
is used to access the DOM node rendered from a component.
Create a ref
object. Then pass it to an React element via the ref
(special) prop.
Finally, log out the value in the onClick
button handler to verify.
Reference: useRef hook reference, react.dev.
Set scrollHeight
of DOM node on button click
Any state that we need to make rendering decisious should be tracked in state.
Set the scrollHeight
of our DOM node, on state, when our button is clicked.
Set maxHeight
of content container with contentHeight
state value
If we want to see our transition, we need to explicitly set the maxHeight
of the content container (CSS stuff 😭).
Use the contentHeight
value (instead of "none"
) to set the expanded
container maxHeight
.
Reference: Using CSS Transitions on Auto Dimensions, CSS Tricks.
Use React.useEffect
to set contentHeight
Setting contentHeight
on click is has a major flaw.
The first click will not saw the transition animation.
Move the setContentHeight
function into a React.useEffect
to set the contentHeight
on every render.
References: useEffect hook reference, react.dev.
Hide button if below height threshhold
This component has no purpose if content height is under our 100px
threshold.
Hide the the “Show more” button if height is less than 100px
.
Use the Logical AND operator (&&
).
Reference: Logical AND operator (&&
), react.dev.
Update when window resizes, with useEffect
Use effect is design to sync events that external to React.
Add a new useEffect that listens for window resize events and updates contentHeight
.
Spot the memory leak with console.log
Not everything can be observed with console.log
.
But useEffect
often can.
Add a console.log
statement to our resize useEffect
to see how often it’s called.
(It’s also easy to see this in the profiler tab of standard Chrome DevTools.)
Refactor the resize useEffect
to call a function by reference
Writing functions inline is extremely convenient.
But it means that we’re creating a new function every render.
Separate the event handler declaration function and the addEventLister
call.
Add useEffect
cleanup function
useEffect
callback functions can return a cleanup function to unmount event listers and prevent memory leaks.
Return a function from the resize useEffect
to remove the event listener.
Reference: Thinking from the Effect’s perspective, react.dev.
Debounce resize event handler
Debouncing is a technique to prevent a function from being called too frequently.
Wrap the handleResize
function in the debounce
utility.
This requires a debounce function.
I’d recommend using this one from lodash. In our case, you can use this one:
Extract custom useEffect
hook
We’re not limited to the hooks included in React.
Extract the resize useEffect
into a custom hook function. Take handleResize
as an argument.
Reference: Custom Hooks, react.dev.
Create Context with React.createContext
Context is a way to share state between components without passing props.
Create a new context, to share expanded
state with React.createContext
.
Reference: createContext hook reference, react.dev.
Provide Context with the Context.Provider
component
Context is provided to children with the Context.Provider
component.
Wrap the ShowMore
render function in the ExpandedContext.Provider
component and pass the expanded
state to it’s value
prop.
Reference: Context.Provider component reference, react.dev
Consume Context with useContext
Contexts are consumed in components with the useContext
hook.
Create a new component that consumes the ExpandedContext
and renders the expanded
state.
Reference: useContext hook reference, react.dev.
Provide value and set function on Context
For actions — like buttons — the components need access to state values and set functions.
Modify the Context Provider
and useContext
consumer to use an array: [expanded, setExpanded]
.
Repeat for any required Contexts
Bonus:
- Put this entire package into a re-usable module and rename accordingly
- Improve accessibility of component