Advanced React Patterns
Advanced React Patterns
• Learning Outcomes
• What We Provide You With
Learning Outcomes #
Hooks are most likely the way we’ll be writing React components for the next
couple of years, so they are quite important to know.
Compound Components
Props Collection
Prop Getters
State Initializers
State Reducers
Control Props
If you’re completely new to these advanced patterns, don’t worry! I’ll explain
them in detail in this course. If you’re familiar with how these patterns work
from previous experiences, I’ll show you how to implement these patterns
with hooks.
Advanced patterns with hooks allow reusable React components to be incredibly exible.
They must be able to extend the functionality and styles of the component as
they deem fit. This is where advanced patterns come into the picture.
I didn’t create these advanced patterns. In fact, most of the advanced React
patterns were made popular by one man, Kent Dodds — an amazing
Javascript engineer.
The community has received these patterns extremely well, and I’m here to
help you understand how they work!
Compound components in React are components that are composed of two or more separate components.
• Introduction
• Example: select
• Quick quiz!
Introduction #
The first pattern we will consider is called the Compound Components
pattern. I know it sounds fancy, but I’ll break it down.
The main component is usually called the parent, and the separate composed
components, children.
But if the children components are used without the outer parent components,
1 of 3
But if the children components are used without the outer parent components,
2 of 3
x
Example: select #
A classic example is the HTML select element.
<select>
<option value="value0">key0</option>
<option value="value1">key1</option>
<option value="value2">key2</option>
</select>
Here, the select element is the parent, and the option elements are children
This works like a compound component. For one, it no sense to use the
<option>key0</option> element without a select parent tag. The overall
behavior of a select element also relies on having these composed option
elements as well. Hence, they are connected to one another.
The state of the entire component is managed by select with all child
e state o t e e t e co po e t s a aged by se ect t a c d
elements dependent on that state.
It is also worth mentioning that compound components are just one of many
ways to express the API for your components.
For example, while it doesn’t look as good, the select element could have
been designed to work like this:
<select options="key:value;anotherKey:anotherValue"></select>
A poor way that the select element could have been designed
This is definitely not the best way to express this API as it makes passing
attributes to the child components almost impossible.
Quick quiz! #
1
What is a compound component?
COMPLETED 0%
1 of 2
With that in mind, let’s take a look at an example that’ll help you understand
and build your own compound components.
Example: Building an Expandable Component
In this lesson, we will set up an expandable component that is based on the compound component pattern
<Expandable>
<Expandable.Header> Header </Expandable.Header>
<Expandable.Icon/>
<Expandable.Body> This is the content </Expandable.Body>
</Expandable>
Unexpanded
Expanded
In the code block above, you’ll notice I have used expressions like this:
Expandable.Header
<Expandable>
<Header> Header </Header>
<Icon/>
<Body> This is the content </Body>
</Expandable>
// state
{
expanded: true || false
}
Show Hint
property, we will also expose a function callback to toggle the value of this
expanded state property.
If that makes sense to you, here’s the starting point for the Expandable
component. We’ll be creating this in a file called Expandable.js
With the basic setup out of the way, let’s do a little more.
The context object was created with no initial value. However, we need the
Provider to expose the state value expanded and a toggle function to update
the state.
With the expanded state variable created, let’s create the toggle updater
function to toggle the value of expanded — whether true or false .
This is the same as what I’ve done above. The function passed to setExpanded
receives the previous value of expanded , i.e., prevExpanded and returns the
opposite of that, !prevExpanded
Quick Quiz! #
Before we move on, let’s take a quick quiz on what we’ve covered so far!
// First
<Expandable>
<Header> Header </Header>
<Icon/>
<Body> This is the content </Body>
</Expandable>
// Second
<Expandable>
<Expandable.Header> Header </Expandable.Header>
<Expandable.Icon/>
<Expandable.Body> This is the content </Expandable.Body>
</Expandable>
COMPLETED 0%
1 of 2
Let’s make some optimizations now. Catch you in the next lesson!
Preventing Unnecessary Renders
Once we have both expanded and toggle created, we can expose these via the
Provider ’s value prop.
This works, but the value reference will be different on every re-render
causing the Provider to re-render its children.
useMemo takes a callback that returns the object value {expanded, toggle} and
we pass an array dependency [expanded, toggle] . This means that the
memoized value remains the same unless the dependencies change.
Quick Quiz! #
Before we move on, let’s take a quick quiz on what we’ve covered so far!
1
What does memoization do in React?
COMPLETED 0%
1 of 2
In the next lesson, we’ll discuss an approach for avoiding unnecessary state
callbacks.
Handling State Change Callbacks
In this lesson, we'll learn how to use React hooks to handle state change callbacks.
this.setState({
name: "value"
}, () => {
this.props.onStateChange(this.state.name)
})
If you don’t have experience with class components, this is how you trigger a
callback after a state change in class components.
this.props.onStateChange(this.state.name)
Why is this important? This is good practice for creating reusable components
because this way, the consumer of your component can attach any custom
logic to be run after a state update.
For example:
Using useEffect #
In most cases, when you want to perform a side effect within a functional
component, you should reach for useEffect .
useEffect(() => {
props.onExpanded(expanded)
}, [expanded])
The problem with this, however, is that the useEffect function is called at
least once — when the component is initially mounted.
So, even though there’s a dependency array, [expanded] , the callback will also
be invoked when the component mounts!
useEffect(() => {
// this function will always be invoked on mount
})
//faulty solution
...
let componentJustMounted = true
useEffect(
() => {
if(!componentJustMounted) {
props.onExpand(expanded)
componentJustMounted = false
}
},
[expanded]
)
...
Loosely speaking, the thinking behind the code is correct. You keep track of a
certain variable componentJustMounted , set it to true , and only call the user
callback onExpand when componentJustMounted is false.
Finally, the componentJustMounted value is only set to false after the user
callback has been invoked at least once.
Looks good.
However, the problem with this is that whenever the function component re-
renders owing to a state or prop change, the componentJustMounted value will
always be reset to true . Thus, the user callback onExpand will never be
invoked as it is only invoked when componentJustMounted is false.
...
if (!componentJustMounted) {
onExpand(expanded)
}
...
Quick Quiz! #
Quiz yourself on what we’ve learned so far.
1 How do you update a component after a state change in traditional React
class components?
COMPLETED 0%
1 of 2
The solution to this problem lies in the next lesson. Catch you there!
Keeping Value Constant
//correct implementation
const componentJustMounted = useRef(true)
useEffect(
() => {
if (!componentJustMounted.current) {
onExpand(expanded)
}
componentJustMounted.current = false
},
[expanded]
)
useRef returns a ref object, and the value stored in the object may be
retrieved from the current property, ref.current
After invoking the user callback, we then update this value to false .
componentJustMounted.current = false
Now, whenever there’s a state or prop change, the value in the ref object isn’t
tampered with. It remains the same.
import React, { createContext, useState, useCallback, useRef, useEffect, useMemo } from 'reac
Q
What is one problem with use useEffect ?
COMPLETED 0%
1 of 1
If you’ve followed along so far, that’s great! We’ve broken down the most
complex component in the bunch. Now, let’s move on to the child components.
Building the Compound Child Components
Now, let’s work on the Expandable component's child components and actually see some output!
These child components need to consume values from the context object
created in Expandable.js .
Now, we may use the useContext hook to consume the values from the
Provider .
//Header.js
Header.js
import React, { useContext } from 'react'
import { ExpandableContext } from './Expandable'
Expandable.js
const Header = ({children}) => {
const { toggle } = useContext(ExpandableContext)
return <div onClick={toggle}>{children}</div>
}
export default Header
Simple, right?
It renders a div whose onClick callback is the toggle function for toggling
the expanded state within the Expandable parent component.
// Body.js
Body.js
import { useContext } from 'react'
import { ExpandableContext } from './Expandable'
Header.js
const Body = ({ children }) => {
Expandable.js const { expanded } = useContext(ExpandableContext)
return expanded ? children : null
}
export default Body
The expanded value is retrieved from the context object and used within the
rendered markup. Line 7 reads like this: expanded, render children ,
otherwise, render nothing.
// Icon.js
Icon.js
import { useContext } from 'react'
import { ExpandableContext } from './Expandable'
Body.js
const Icon = () => {
Header.js const { expanded } = useContext(ExpandableContext)
return expanded ? '-' : '+'
Expandable.js }
export default Icon
With all child components built, we can set them as Expandable properties.
See below:
import Header from './Header'
import Icon from './Icon'
import Body from './Body'
...
<Expandable>
<Expandable.Header>React hooks</Expandable.Header>
<Expandable.Icon />
<Expandable.Body>Hooks are awesome</Expandable.Body>
</Expandable>
Current Look #
Here is the Expandable component so far!
// Body.js
import { useContext } from 'react'
import { ExpandableContext } from './Expandable'
Quick Quiz! #
1
Select all that apply for this question.
This works but it has to be the ugliest component I’ve ever seen. We can do
better. Let’s try in the next lesson!
Manageable Styling for Reusable Components
Love it or hate it, styling (or CSS) is integral to how the web works.
While there’s a number of ways to style a React component - and I’m sure you
have a favorite - when you build reusable components, it’s always a good idea
to expose a frictionless API for overriding default styles.
Usually, I recommend having your components be styled via both style and
className props.
For example:
Now, our goal isn’t just styling the component, but to make it as reusable as
possible. This means letting the consumer style the component whichever
way they want, whether that be using inline styles via the style prop, or by
passing some className prop.
Styling the Header Component #
Let’s begin with the Header child component; have a look at Header.js .
// Body.js
import { useContext } from 'react'
import { ExpandableContext } from './Expandable'
First, let’s change the rendered markup to a button . It’s a more accessible
alternative to the div used earlier.
// Body.js
import { useContext } from 'react'
import { ExpandableContext } from './Expandable'
Adding CSS #
We will now write some default styles for the Header component in a
Header.css file.
// Body.js
import { useContext } from 'react'
import { ExpandableContext } from './Expandable'
I’m sure you can figure out the simple CSS above. If not, don’t stress it. What’s
important is to note the default className used here, .Expandable-trigger .
To apply these styles, we need to import the CSS file and apply the
appropriate className prop to the rendered button .
// Body.js
import { useContext } from 'react'
import { ExpandableContext } from './Expandable'
This will apply the styling we’ve written in the CSS file, but it doesn’t take into
account any className prop passed in by the user.
// Body.js
import { useContext } from 'react'
import { ExpandableContext } from './Expandable'
// Body.js
import { useContext } from 'react'
import { ExpandableContext } from './Expandable'
Having provided a default value, if the user still doesn’t enter a className , the
combinedClassName value will be equal to "Expandable-trigger " . Note the
empty string appended to the Expandable-trigger . This works because of the
way template literals work.
// Body.js
import { useContext } from 'react'
import { ExpandableContext } from './Expandable'
The JavaScript join() method concatenates an array of strings into one single
array separated by a ‘separator’ passed as an argument to join() . So on line
8, we concatenate the string Expandable-trigger with the passed className
not separated by anything.
// Body.js
import { useContext } from 'react'
import { ExpandableContext } from './Expandable'
So far, so good.
Quick Quiz! #
1
Why do we want the component to be stylable via both style and
className props?
COMPLETED 0%
1 of 7
So far, we’ve covered the className prop. What about the option to override
default styles by passing a style prop? Let’s write code to allow that in the
next lesson!
Custom Styling Options
• Styling
• Styling Body and Icon
• Body Component Before
• Body Component After
• The Icon Component Before
• The Icon Component After
• Styling Expandable
• Quick Quiz!
Styling #
So far, we’ve handled styling with the className prop. What about the option
to override default styles by passing a style prop? As of right now, we can’t
do that
Instead of explicitly destructuring the style prop, we can pass any other prop
passed by the user to the button component.
// Body.js
import { useContext } from 'react'
import { ExpandableContext } from './Expandable'
With this done, the Header component receives our default styles, while
allowing for change via the className or style props. Here’s how you would
pass styles to the Header component.
Let’s try passing some styles! Let’s override via className first. Have a look at
App.js .
// Body.js
import { useContext } from 'react'
import { ExpandableContext } from './Expandable'
Of course, this does not have any effect right now because we have not
defined anything for this new className . Let’s override style via style prop
now.
// Body.js
import { useContext } from 'react'
import { ExpandableContext } from './Expandable'
Now, I’ll go ahead and do the same for the other child components, Body and
Icon .
// before
const Body = ({ children }) => {
const { expanded } = useContext(ExpandableContext)
return expanded ? children : null
}
Have a look at the Body.js file for changes to the body component. Note that
we’ve created a Body.css file.
return expanded ? (
<div className={combinedClassNames} {...otherProps}>
{children}
</div>
) : null
}
// before
const Icon = () => {
const { expanded } = useContext(ExpandableContext)
return expanded ? '-' : '+'
}
return expanded ? (
<div className={combinedClassNames} {...otherProps}>
{children}
</div>
) : null
}
Notice the + and - move to the far right because of this styling!
Styling Expandable #
And finally, some styles for the parent component, Expandable .
.Expandable-panel {
margin: 0;
padding: 1em 1.5em;
border: 1px solid hsl(216, 94%, 94%);;
min-height: 150px;
}
1 of 2
2 of 2
Quick Quiz! #
1
What method of overriding is being used here?
COMPLETED 0%
1 of 3
This is great! Our component is stylable and is great for our use case. Let’s
look at how someone else might consume it for their usecase.
Custom Use of the Component
1 of 2
2 of 2
.Expandable-panel {
margin: 0;
padding: 1em 1.5em;
border: 1px solid hsl(216, 94%, 94%);;
min-height: 150px;
}
Final Look #
You can go one step further to test if overriding styles via the style prop
works as well.
.Expandable-panel {
margin: 0;
padding: 1em 1.5em;
border: 1px solid hsl(216, 94%, 94%);;
min-height: 150px;
}
We’ve built a component that other developers can style and use however
they want. But what if they want to extend it to add more functionality? We’ll
look at a design pattern in the next chapter that allows us to do exactly that!
What are Control Props?
Assume another developer, who we’ll refer to as “user”, decided to use the
component we’ve built, but in a much different way.
This user has found some very good information they think is worth sharing.
Within their app, they store this information in an array:
// user's app
const information = [
{
header: 'Why everyone should live forever',
note: 'This is highly sensitive information ... !!!!'
},
{
header: 'The internet disappears',
note:
'I just uncovered the biggest threat...'
},
{
header: 'The truth about Elon musk and Mars!',
note: 'Nobody tells you this...'
}
]
In their App , they loop over the list of information and render our Expandable
component with its children as seen below:
.Expandable-panel {
margin: 0;
padding: 1em 1.5em;
border: 1px solid hsl(216, 94%, 94%);;
min-height: 150px;
}
When the user clicks any of the headers, the contents expand as designed. Try
clicking any one of the headers. One of the elements expands.
Well, that header expands as well. This is exactly how the internals of our
Expandable component works.
As you can see from the output of the code above, both clicked headers
expand their content. Unfortunately, this is not the behavior the user seeks.
What the user really wants is behavior such that only one container expands
at a time. If the user clicks another header, it should open but the rest should
close.
Quick Quiz! #
1
What is the issue with the expandable component at the moment?
COMPLETED 0%
1 of 3
So, how do we extend the Expandable component to handle this use case?
Well, this is where the control props pattern comes into play. Note that I’m
using the compound component we built as an example. In reality, you can
implement this functionality in any component you wish. In the next lesson,
we’ll learn how the control props pattern works.
How Control Props Work
In this lesson, we'll make the Expandable component work so that the elements expand one at a time
<input />
A controlled input component has its value and onChange handler managed
externally.
This is exactly what we aim for when implementing the control props pattern.
We want the internally-managed state to be manageable from the outside via
props!
Implementing in Expandable #
How it Works #
The implementation is quite simple, but we need to understand how it works
before we delve into writing some code.
<Expandable>
<Expandable.Header>
{someHeader}
</Expandable.Header>
<Expandable.Icon />
<Expandable.Body>{someContent}</Expandable.Body>
</Expandable>
For example:
<Expandable shouldExpand={someUserDefinedBooleanValue}>
<Expandable.Header>
{someHeader}
</Expandable.Header>
<Expandable.Icon />
<Expandable.Body>{someContent}</Expandable.Body>
</Expandable>
Actual Implementation #
Now, let’s move on to the actual implementation.
First, we will check if the control prop shouldExpand exists. If it does, then the
component is a controlled component, i.e., expanded managed externally.
// Expandable.js
const Expandable = ({
shouldExpand, // 👈 take in the control prop
...otherProps
}) => {
// see check 👇
Not everyone would pass in the control prop. It’s possible that a lot of users
just go for the default use case of letting us handle the state internally. This is
why we check to see if the control prop is defined.
It all begins with the value that the Expandable component communicates to
the child elements.
// before
const value = useMemo(() => ({ expanded, toggle}), [
expanded, toggle
])
Before, we passed along the expanded and toggle values from the internals of
the Expandable component.
// Expanded.js
...
const getState = isExpandControlled ? shouldExpand : expanded
...
where shouldExpand is the user’s control prop and expanded is our internal
state.
Be sure to remember that a controlled input element is passed the value prop
and onChange prop. A controlled component receives both the state value
and the function handler as well.
Before now, we had an internal toggle function.
// Expandable.js
...
const getToggle = isExpandControlled ? onExpand : toggle
...
With these done, here’s the value communicated to the child elements:
...
const value = useMemo(() => ({ expanded: getState, toggle: getToggle }), [
getState,
getToggle
])
Note how getState and getToggle are both used to get the desired expanded
state and toggle function depending on whether the component is controlled
or not.
We’re pretty much done implementing the control prop pattern here.
When the component wasn’t controlled, the onExpand prop was a user-defined
function invoked after every state change. That’s not going to be relevant if
the component is controlled. Why? The user handles the toggle function
themselves when the component is controlled. They can choose to do anything
in the callback with the already controlled state value too.
Based on the explanation above, we need to make sure the useEffect call
doesn’t run when the component is controlled.
// Expandable.js
...
useEffect(
() => {
// run only when component is controlled.
// see !isExpandControlled 👇
if (!componentJustMounted && !isExpandControlled) {
onExpand(expanded)
componentJustMounted.current = false
}
},
[expanded, onExpand, isExpandControlled]
)
...
.Expandable-panel {
margin: 0;
padding: 1em 1.5em;
border: 1px solid hsl(216, 94%, 94%);;
min-height: 150px;
}
I have yet to show you how it solves the user’s problem and how to use it in
the user’s app. We’ll learn that next! But before that, let’s take a quick quiz.
Quick Quiz! #
1
What makes a component a controlled component?
COMPLETED 0%
1 of 3
Hope you got those right! Catch you in the next lesson.
Using the Controlled Component
In this lesson, we'll learn how a user would use a controlled component from an app!
• Sending a Prop
• How The data-index Property Helps
• Setting the Active Index
• The Final Output
• Quick Quiz
Sending a Prop #
We reach out to the user and let them know that we have implemented a
pattern to cater for their specific use case.
Here’s how.
Within the user app, they send a data-index prop to every Header element:
// before
<Expandable.Header />
// now
<Expandable.Header data-index={index} />
function App () {
...
// see index below 👇
{information.map(({ header, note }, index) => (
...
)}
}
How The data-index Property Helps #
The returned element from the header gets a data-index property.
// user's app
function App () {
const [activeIndex, setActiveIndex] = useState(null)
const onExpand = evt => setActiveIndex(evt.target.dataset.index)
return (
<div className='App'>
{information.map(({ header, note }, index) => (
<Expandable
shouldExpand={index === +activeIndex}
onExpand={onExpand}
key={index}
>
<Expandable.Header
style={{ color: 'red', border: '1px solid teal' }}
data-index={index}
>
{header}
</Expandable.Header>
<Expandable.Icon />
<Expandable.Body>{note}</Expandable.Body>
</Expandable>
))}
</div>
)
}
Note the shouldExpand controlled prop being the result of index ===
+activeIndex .
activeIndex is a state variable within the user app. Whenever any header is
clicked, the activeIndex is set to the data-index of the element.
This way the user knows which header was just clicked and should be active
at that given time.
This explains why they’ve defined the control prop shouldExpand as shown
below:
This is what’s invoked on every click, NOT our default toggle function. When
the user clicks on a header, the activeIndex state variable is set to the data-
index from the click target.
With this, the user implements their feature to their satisfaction. No two
headers are expanded at the same time.
Quick Quiz #
Let’s take a quick quiz before we move on.
1
What does the === operator do in JavaScript?
COMPLETED 0%
1 of 3
With our controlled props API, we’ve made it relatively easy for the user to
control the state of the Expanded component. How convenient! Let’s build a
custom hook next.
Building a Custom Hook
• Introduction
• useExpanded Custom Hook
• useEffectAfterMount Custom Hook
• Using Custom Hooks
• Quick Quiz!
Introduction #
Before we go into the other advanced patterns, it’s important to understand
the default way to share functionality with hooks — building a custom hook.
So far, we’ve built a compound component that works great! Let’s say you
were the author of some open-source library, and you wanted to expose the
“expand” functionality via a custom hook, how would you do this?
First, let’s agree on the name of your open-source (OS) library. We’ll leave it as
Expandable — same as before.
Now, instead of having the logic for managing the expanded state in an
Expandable component that returns a Provider , we can just export 2 custom
hooks.
// Expandable.js
import useExpanded from '../useExpanded'
import useEffectAfterMount from '../useEffectAfterMount'
export { useExpanded as default, useEffectAfterMount }
Note that these custom hooks will pretty much use the same logic as the
Expandable compound component we had written earlier. The difference here
will be wrapping these in a custom hook.
.Expandable-panel {
margin: 0;
padding: 1em 1.5em;
border: 1px solid hsl(216, 94%, 94%);;
min-height: 150px;
}
If you would like a refresher on the internal logic of this custom hook, feel
free to look at the section on compound components.
.Expandable-panel {
margin: 0;
padding: 1em 1.5em;
border: 1px solid hsl(216, 94%, 94%);;
min-height: 150px;
}
The only difference here is that useEffectAfterMount doesn’t return any value.
Rather, it invokes the useEffect hook. To make this as generic as possible, the
custom hook takes in two arguments, the callback to be invoked after mount,
and the array dependencies on which the useEffect function relies.
Also, note that line 8 reads, return cb() . This is to handle unsubscriptions by
returning whatever is returned by the callback.
Great!
.Expandable-panel {
margin: 0;
padding: 1em 1.5em;
border: 1px solid hsl(216, 94%, 94%);;
min-height: 150px;
}
The consumer imports your useExpanded custom hook and invokes the
function to retrieve expanded and toggle .
With these values, they can render whatever they want. We provide the logic
and let the user render whatever UI they deem fit. By default, they render a
button.
This button invokes the exposed toggle function from the custom hook.
Based on the toggled expanded state, they render smileys.
Like the render props API, we’ve given control over to the user to render
whatever UI they want while we handle whatever logic is required for them
to do so.
To use the useEffectAfterMount hook, the user needs to do something like the
following; have a look at App.js .
.Expandable-panel {
margin: 0;
padding: 1em 1.5em;
border: 1px solid hsl(216, 94%, 94%);;
min-height: 150px;
}
Now, whenever the button is clicked, they’ll get the logs in the console!
Quick Quiz! #
Let’s take a quiz. You know the drill.
1
What is the function cb() in the useEffectAfterMount custom hook?
COMPLETED 0%
1 of 4
Now that we know how to build and use custom hooks, let’s make the user’s
life even easier and provide them with some default UI elements.
Providing Default UI Elements
We'll now make our open source library more user-friendly and convenient by allowing export of default UI
elements,
You could also export some default Header , Body and Icon elements built to
make their styling more configurable.
The child elements for the compound component required access to some
context object. We may get rid of that and solely base the contract on props.
// Body.js (before)
const Body = ({ children, className = '', ...otherProps }) => {
...
// look here 👇
const { expanded } = useContext(ExpandableContext)
return ...
}
// now (takes in expanded as props)
const Body = ({
children, className = '', expanded, ...otherProps }) => {
...
return ...
}
The same can be done for the Icon and Header components.
It’s important to note that you don’t always have to do this. You can always
share custom functionality by just writing a custom hook.
Quick Quiz! #
Test yourself on this lesson!
1
Why are we using our custom UI elements here?
COMPLETED 0%
1 of 2
We’ve done a brilliant job building this imaginary OS library, huh? In the
following chapters, we’ll incorporate even more advanced patterns that users
will find incredibly useful.
What is the Props Collection Pattern?
In this lesson, we'll have a quick overview of the props collection pattern!
Regardless of the UI solution the user decides to go for, they’ll always need an
onClick handler for the “toggle element”.
Regardless of the element they choose to render, every user still needs to
handle the onClick callback.
Depending on your use case, there could be more “common props” associated
with your reusable hook or component.
Is there a way we can prevent the users from writing these props every single
time? Can we expose some API from within your reusable custom hook or
component?
These are the questions that the props collection pattern answers with a
resounding yes!
Quick Quiz! #
1
What’s the main advantage of the props collection pattern?
COMPLETED 0%
1 of 2
In the next lesson, we’ll look at this pattern in action!
The Props Collection Pattern in Practice
...
return value
}
Note that we’ve called this props collection togglerProps because it is a prop
collection for the toggler, i.e., the toggle element, regardless of which UI
element it is.
We’ll then expose the togglerProps from the custom hook via the returned
value variable.
return value
}
Final Output #
With togglerProps now exposed, it can be consumed by any user as shown
below. Have a look at useExpanded.js .
.Expandable-panel {
margin: 0;
padding: 1em 1.5em;
border: 1px solid hsl(216, 94%, 94%);;
min-height: 150px;
}
This works just like before, except that the user didn’t have to manually write
the “common props” for the toggle element.
Here is the expected output of the app. aria attribute also added to the DOM element.
console.log(obj)
If you wanted to create the same object but allow for some level of
customization, you could do this:
Now, this objCreator function can be called multiple times to create different
objects as shown below:
console.log(obj1)
console.log(obj2)
console.log(obj3)
Because this is the difference between the props collection pattern and prop
getters pattern.
// before
const togglerProps = useMemo(
() => ({
onClick: toggle,
'aria-expanded': expanded
}),
[toggle, expanded]
)
// now
const getTogglerProps = useCallback(
() => ({
onClick: toggle,
'aria-expanded': expanded
}),
[toggle, expanded]
)
We’ll expose this via the returned value variable within the useExpanded hook
as shown below:
// before
<button {...togglerProps}>Click to view awesomeness...</button>
// now
<button {...getTogglerProps()}>Click to view awesomeness...</button>
And that’s it! The user’s app works just like before.
Quick Quiz #
Let’s take a quick quiz before moving on!
1
What is the main difference between the prop getters pattern and the
props collection pattern?
COMPLETED 0%
1 of 2
I know what you’re thinking. If we’re not going to pass any arguments to the
getTogglerProps function, why bother with the refactor?
In this lesson, we'll learn how users can pass additional props to prop getters!
If the user needs some more props that we haven’t catered for in the prop
collection, they could do this:
<button
{...getTogglerProps({
id: 'my-btn-id',
'aria-label': 'custom toggler'
})}
>
Click to view awesomeness...
</button>
It’s arguably more expressive. So, let’s extend the getTogglerProps function to
handle this use case.
...
const getTogglerProps = useCallback(
({ ...customProps }) => ({
onClick: toggle,
'aria-expanded': expanded,
...customProps
}),
[toggle, expanded]
)
Since the user will invoke getTogglerProps with an object of custom props, we
could use the rest parameter to hold a reference to all the custom props.
({ ...customProps }) => ({
})
Then we could spread over all custom props into the returned props
collection.
({ ...customProps }) => ({
onClick: toggle,
'aria-expanded': expanded,
// look here 👇
...customProps
})
With this extension to getTogglerProps we’ve handled the case for custom
props not catered to in the default props collection we expose.
.Expandable-panel {
margin: 0;
padding: 1em 1.5em;
border: 1px solid hsl(216, 94%, 94%);;
min-height: 150px;
}
How interesting!
Quick Quiz #
Let’s take a quick quiz before moving forward
Q
What would happen if we passed custom props like so?
({ customProps }) => ({
...
COMPLETED 0%
1 of 1
In this lesson, we'll see how two prop properties with the same name can be invoked at once.
We allowed passing custom props into the getToggler props getter. Now, what
if the user wanted to pass in a custom onClick prop?
.Expandable-panel {
margin: 0;
padding: 1em 1.5em;
border: 1px solid hsl(216, 94%, 94%);;
min-height: 150px;
}
If the user does this, it completely overrides the onClick handler within the
useExpanded custom hook and breaks the functionality of the app as you can
see in the output.
The button no longer expands the element, but the user’s custom click handler
is invoked whenever you click the button.
({ ...customProps }) => ({
onClick: toggle, // this is overriden
'aria-expanded': expanded,
// customProps overrides the onClick above
...customProps
}),
[toggle, expanded]
)
Here’s how:
.Expandable-panel {
margin: 0;
padding: 1em 1.5em;
border: 1px solid hsl(216, 94%, 94%);;
min-height: 150px;
}
Now instead of returning an object with the onClick set to a single function,
i.e, our internal toggle function or the user s onClick , we could set the
...
onClick: callFunctionsInSequence(toggle, onClick)
Also, note how we destructure the user’s onClick handler and use the rest
parameter to handle the other props passed in. To avoid getting an error if no
object is passed in by the user, we set a default parameter value of {} .
...
// note destructuring and default parameter value of {}
({ onClick, ...props } = {}) => ({
})
Now back to the elephant in the room; how do we write a function that takes
one or two functions and invokes all of them without neglecting the passed
arguments?
This function cannot break if any of the function arguments are undefined
either.
When we do this:
...
onClick: callFunctionsInSequence(toggle, onClick) // this invokes callFunctionsInSequence
The return value saved in the onClick object property is the returned
function from invoking callFunctionsInSequence :
(...args) => fns.forEach(fn && fn(...args))
If you remember from basic React, we always attach click handlers like this:
...
render() {
return <button onClick={this.handleClick} />
}
The function receives whatever arguments are passed into the function,
(...args) , and invokes all function parameters with these arguments if the
function is not false. fn && fn(...args) .
In this case, the argument received by the function will be the event object
and it will be passed down to both toggle and onClick as arguments.
The callFuncstionsInSequence Function
The full implementation of the custom hook now looks like this:
.Expandable-panel {
margin: 0;
padding: 1em 1.5em;
border: 1px solid hsl(216, 94%, 94%);;
min-height: 150px;
}
Quick Quiz! #
Q
What is the suggested solution to the issue of props with the same
property name?
COMPLETED 0%
1 of 1
With this addition, our internal click handler, toggle , and the user’s custom
handler, onClick , are both invoked when the toggle button is clicked!
How amazing! We wouldn’t be able to cater for this case with just a prop
collection object. Thanks to prop getters, we have more power to provide
reusable components.
From the next chapter on, we’re going to be looking at a new pattern: State
Initializers. Catch you in the next lesson!
What is the State Initialization Pattern?
Note that the state initializer pattern doesn’t give full control over setting the
“value of the state” every single time. It mostly allows for setting initial state
resetting it.
This is not the same as having full control over setting the state value, but it
offers similar benefits as you’ll see soon.
We’ve assumed that the initial state passed to the useState call will be false
every single time.
That may not be the case.
Let’s have this value controllable from an argument the user can pass in. Let’s
call this parameter initialExpanded as seen below:
Now the user can pass in initialExpanded into the useExpanded custom hook
call to decide the initial state of the expanded component.
Here’s an example where a user may want the terms and conditions content
expanded by default:
// user's app
function App () {
// look here - "true" passed into hook call 👇
const { expanded, toggle } = useExpanded(true)
return (
...
)
}
With our previous setup, this was not an easy feat for the user because we had
internally hardcoded the initial state as false .
Quick Quiz! #
Time for a quiz!
Q
What key problem does this pattern address?
COMPLETED 0%
1 of 1
In the next lesson, we’ll have a look at how the state can be reset!
Resetting the State
While we’ve made it easier for the user to dictate the initial state within the
custom hook, they should also be able to reset the state to the initial state at
any point in time, i.e., a reset callback they can invoke to reset the state to the
initial default state they provided.
Assume the terms and conditions content in the user app was so long that they
changed the default expanded state to false , i.e., the expandable content isn’t
open by default.
Since the content was long, they decided to provide a button towards the end
of the write-up. A reader could click to revert the expandable content to the
initial closed state for which they may want to close the expandable content
and perform some cleanups.
Even though this particular example isn’t the most realistic for the reset
functionality, in larger applications you can solve the problem using the same
method discussed here.
All the reset function should do is set the “expanded” state back to the
default provided, i.e., initialExpanded
The code’s simple enough. All the reset function does is call setExpanded with
the initialExpanded value to reset the state back to the initial state supplied
by the user.
Remember, this consumer of our custom hook wants to close the terms and
conditions body and also perform some cleanup/side effect.
How will this user perform the cleanup after a reset? We need to cater to this
use case as well.
Let’s get some ideas from the implemented solution for the user to run custom
code after every change to the internal expanded state.
Now we need to make the same possible after a reset is made. Before I explain
the solution to that, have a look at the usage of useEffectAfterMount in the
code block above.
Now, for the regular state update, the user just had to pass in the array
dependency [expanded] to get the effect function to run after every expanded
state update.
// user's app
const { resetDep } = useExpanded(false)
useEffectAfterMount(
() => {
console.log('reset cleanup in progress!!!!')
},
[resetDep]
)
...
We need to provide a reset dependency the user can pass into the
useEffectAfterMount array dependency.
Well, the first thing that comes to mind is a state value to be set whenever the
user invokes the reset callback. The state value will keep track of how many
times a reset has been made.
// useExpanded.js
...
const [resetDep, setResetDep] = useState(0)
const reset useCallback(
() => {
// perform actual reset
setExpanded(initialExpanded)
// increase reset count - call this resetDep
setResetDep(resetDep => resetDep + 1)
},
[initialExpanded]
)
...
// useExpanded.js
...
const value = useMemo(
() => ({
expanded,
toggle,
getTogglerProps,
reset,
resetDep
}),
[expanded, toggle, getTogglerProps, reset, resetDep]
)
return value
...
The Output #
This works just as expected!
.Expandable-panel {
margin: 0;
padding: 1em 1.5em;
border: 1px solid hsl(216, 94%, 94%);;
min-height: 150px;
}
There’s arguably one problem with the solution. Should we really be saving
the reset count as a new state variable?
Quick Quiz! #
Time for a quiz.
Q
Why is a reset callback important?
COMPLETED 0%
1 of 1
We’ll have a look at this in the next lesson, however, note that this is just
another case of personal preference. There’s nothing technically wrong with
the solution above.
Getting Rid of Extra State Variables
The solution is similar, it’s only an internal change within our custom hook,
useExpanded . How the user consumes resetDep remains the same.
// useExpanded.js
...
const resetRef = useRef(0)
const reset = useCallback(
() => {
// perform actual reset
setExpanded(initialExpanded)
// update reset count
++resetRef.current
},
[initialExpanded]
)
...
// expose resetDep within "value"
...
resetDep: resetRef.current
And that’s it! Same functionality, different approaches. Feel free to use
whichever feels right to you. I pick the second though :)
As a summary, remember that with the state initializer pattern you offer the
user the ability to decide the initial state within your custom hook. You allow
for resetting and invoking a custom function after a reset is made as well.
Even if you had a more complex custom hook with multiple state values, you
could still use this technique. Perhaps, receive the initial state passed in by the
user as an object. Create a memoized initialState object from the user’s state
and use this within your custom hook.
If you also choose to manage your internal state via useReducer , you can still
apply this pattern.
The point is, regardless of your implementation, the goal remains the same; to
provide the user with the ability to decide the initial state and allow them to
reset and invoke custom callbacks after a reset is made.
.Expandable-panel {
margin: 0;
padding: 1em 1.5em;
border: 1px solid hsl(216, 94%, 94%);;
min-height: 150px;
}
Quick Quiz! #
Lets take a quick quiz before moving on!
Q
Why have we decided not to keep resetDep as a state variable?
COMPLETED 0%
1 of 1
In the next chapter, we’ll take things a notch higher as we give even more
control to the users of our components. Remember, that’s what building
highly reusable components is about!
What Is the State Reducer Pattern?
When we write custom hooks or components, they most likely have some way
to keep track of the internal state. With the state reducer pattern, we give
control to the user of our custom hook/component on how the state is updated
internally.
The typical flow for communicating updates to the user of your custom hook
or component looks like this:
Communicating updates in custom hooks/components
This is how all the patterns we’ve looked at so far have worked.
Here’s an illustration:
New internal state updation pattern
With state reducers, what’s important to note is that before we make any
internal state update, we send our proposed changes over to the user. If the
user is okay with the changes, we go ahead and update state with our
proposed changes. If they need to override a state change, they can do that as
well.
Quick Quiz! #
Let’s take a quiz on how the state reducer pattern works.
1
How do updates occur in other patterns?
COMPLETED 0%
1 of 2
Let’s dive right into the implementation-level details of this pattern in the next
lesson!
Refactoring to `useReducer`
• Refactoring to useReducer
• Reducers
• End Result of This Chapter
Refactoring to useReducer #
The most intuitive way to make the state reducer pattern work is using the
useReducer hook to manage the internal state.
First, let’s refactor our custom hook to use useReducer instead of useState .
To refactor our custom hook to use the useReducer hook for state updates,
everything within the custom hook stays the same except how the expanded
state is managed.
First, we’ll change the state from just a boolean value, true or false , to an
object like this: {expanded: true || false} .
// useExpanded.js
export default function useExpanded (initialExpanded = false) {
const initialState = { expanded: initialExpanded }
...
}
// useExpanded.js
// before
...
const [expanded, setExpanded] = useState(initialExpanded)
// now
const [{ expanded }, setExpanded] = useReducer(internalReducer, initialState)
...
Note how we need to destructure expanded from the state object in the new
implementation on line 7. It’s worth mentioning that setExpanded returned
from the useReducer call now works as a dispatcher. I’ll show the implication
of this soon.
useReducer(internalReducer, initialState)
Reducers #
If you’ve worked with Redux in the past, then you’re most likely familiar with
the concept of reducers. If not, a reducer is just a function that receives state
and action to return a new state.
action usually refers to an object, but useReducer isn’t strict about this. If we
stick to the Redux convention, a state update is always made by “dispatching”
an action.
The “dispatcher” is the second value returned by the useReducer call. Also, in
order for the reducer to identify each action being dispatched, a type
property always exists in the action.
// for example
action = {
type: "AN_ACTION_TYPE"
}
I don’t want to turn this into a redux guide, but if you need a refresher, I
wrote a visual guide that will make the concepts of reducers and actions all
sink in.
With the useReducer call in place, we need to actually write the reducer used
within the call, i.e., internalReducer
Here’s the implementation:
// useExpanded.js
const internalReducer = (state, action) => {
switch (action.type) {
case useExpanded.types.toggleExpand:
return {
...state,
expanded: !state.expanded //toggle expand state property
}
case useExpanded.types.reset:
return {
...state,
expanded: action.payload // reset expanded with a payload
}
default:
throw new Error(`Action type ${action.type} not handled`)
}
}
Remember, I said actions typically have a type property. The reducer checks
this type property and returns a new state based on the type.
To stay consistent and prevent unwanted typos, the available types for the
useExpanded custom hook are centralized within the same file.
// useExpanded.js
...
useExpanded.types = {
toggleExpand: 'EXPAND',
reset: 'RESET'
}
That explains why the types are used this way in the code as shown below:
Note that the returned value from the reducer represents the new state of the
ote t at t e etu ed a ue o t e educe ep ese ts t e e state o t e
expanded state. For example, here’s the returned value for the type,
useExpanded.types.toggleExpand .
...
return {
...state,
expanded: !state.expanded
}
...
For this to come full circle, consider how the reset function now works.
// before
setExpanded(initialExpanded)
// now
...
setExpanded({
type: useExpanded.types.reset,
payload: initialExpanded // pass a payload "initialExpanded"
})
Note how the reducer returns the new state based on this dispatched reset
action:
...
case useExpanded.types.reset:
return {
...state,
expanded: action.payload // reset expanded with the payload
}
...
// useExpanded.js
export default function useExpanded (initialExpanded = false) {
// keep initial state in a const variable
// NB: state is now an object e.g {expanded: false}
const initialState = { expanded: initialExpanded }
The available types for state updates are then centralized to prevent string
typos.
// useExpanded.js
...
useExpanded.types = {
toggleExpand: 'EXPAND',
reset: 'RESET'
}
// before
setExpanded(initialExpanded)
// now
...
setExpanded({
type: useExpanded.types.reset,
payload: initialExpanded // pass a payload "initialExpanded"
})
...
Please have a look one more time. This will only prove difficult if you don’t
know how useReducer works. If that’s the case, I suggest you quickly check
that out.
// user's app
...
const { expanded, toggle, reset, resetDep} = useExpanded(false)
For example:
// user's app
function App () {
const { expanded, toggle, reset, resetDep } = useExpanded(
false,
appReducer // 👈 the user's reducer
)
...
}
.Expandable-panel {
margin: 0;
padding: 1em 1.5em;
border: 1px solid hsl(216, 94%, 94%);;
min-height: 150px;
}
Let’s learn how this particular user used our custom hook piece-by-piece in
the rest of this chapter!
The User’s Application
In this lesson, we'll look at how our custom hook can be used in an application
• Scenario
• Calling Toggle When Header is Clicked
• Viewing the Secret Document
• What We Have So Far
Scenario #
Our user is a hacker who has found proof of a big conspiracy on Mars. To
share this with the world, they want to use our useExpanded hook and the
default UI elements we provide to build the following expandable view:
What’s more interesting is that the hacker only wants a reader to view this
secret once. It’s such a big conspiracy.
The hacker’s goal is that whenever the user clicks the button to view the
secret, the expandable content is reset to the default unexpanded state. After
that, the reader can click all they want on the header but the content won’t be
expanded.
Do you see why the user needs the state reducer pattern?
...
// look here 👇
<Header toggle={toggle} style={{ border: '1px dotted red' }}>
They've been lying to you
</Header>
...
Internally, the toggle function always toggles the expanded state property.
The problem is, when the reader has viewed the secret, the hacker wants
further clicks on the Header not to trigger a state update.
They attach a click handler to the button which points to the reset callback
we provide.
...
<section className='App'>
...
<Body>
<p>
Click to view the conspiracy <br />
<button onClick={reset}> View secret </button>
</p>
</Body>
</div>
</section>
The reset callback resets the expanded state to the initial expanded state of
false provided by the hacker.
...
const { expanded, toggle, reset, resetDep} = useExpanded(
false)
The initial state provided by the hacker is false — this means the expandable
content is closed after the click.
Good.
...
useEffectAfterMount(
() => {
// open secret in new tab 👇
window.open('https://leanpub.com/reintroducing-react', '_blank')
// can do more e.g 👉 persist user details to database
},
[resetDep]
)
...
So far the hacker is happy with the API we’ve provided. We just need to
handle their last use case for preventing further state updates on clicking the
Header element after a reader has viewed the secret once.
.Expandable-panel {
margin: 0;
padding: 1em 1.5em;
border: 1px solid hsl(216, 94%, 94%);;
min-height: 150px;
}
In the next lesson, we’ll apply the state reducer pattern to our application.
Implementing the State Reducer Pattern
Let’s integrate the state reducer pattern into the scenario we studied earlier
• Implementation
• Resolving User and Internal Implementation
• The App So Far
• Quick Quiz!
Our solution to the hacker’s feature request is to implement the state reducer
pattern.
When the reader clicks the Header element, we’ll communicate the changes
we propose to be made internally to the hacker, i.e., we’ll let the hacker know
we want to update the expanded state internally.
The hacker will then check their own application state. If the reader has
already viewed the secret resource, the hacker will communicate to our
custom hook NOT to allow a state update.
There it is. The power of the state reducer pattern, giving control of the
internal state updates to the user.
Implementation #
Now, let’s write the technical implementation.
First, we’ll expect a second argument from the hacker, their own reducer.
After the refactor to useReducer , we got the value of the expanded state by
calling our internal reducer.
A reducer always returns new state. The value internalChanges holds the new
state we propose internally by invoking our internalReducer with the current
state and action.
Right now, we invoke the user’s reducer with the internal state and an action
object.
Note that the action object we send to the user is slightly modified. It contains
the action being dispatched and our internal changes!
{
... action,
internalChanges: internalChanges
}
Every reducer takes in state and action to return a new state. It is the
responsibility of the user’s reducer to return whatever they deem fit as the
new state. The user can either return our changes if they’re happy with them
OR override whatever changes they need to.
// useExpanded.js
...
const resolveChangesReducer = (currentInternalState, action) => {
const internalChanges = internalReducer(currentInternalState, action)
const userChanges = userReducer(currentInternalState, {
...action,
internalChanges
})
// look here 👇
return userChanges
}
...
Have a look at the complete implementation of the state reducer. I’ve omitted
whatever hasn’t changed. You can look in the source yourself at the end of the
lesson if you care about the entire source code (and you should!)
// necessary imports 👇
import { useCallback, useMemo, useRef, useReducer } from 'react'
...
// our internal reducer 👇
const internalReducer = (state, action) => {
switch (action.type) {
case useExpanded.types.toggleExpand:
return {
...state,
expanded: !state.expanded
}
case useExpanded.types.reset:
return {
...state,
expanded: action.payload
}
default:
throw new Error(`Action type ${action.type} not handled`)
}
}
// the custom hook 👇
export default function useExpanded (initialExpanded = false, userReducer) {
const initialState = { expanded: initialExpanded }
const resolveChangesReducer = (currentInternalState, action) => {
const internalChanges = internalReducer(
currentInternalState,
action
)
const userChanges = userReducer(currentInternalState, {
...action,
internalChanges
})
return userChanges
}
// the useReducer call 👇
const [{ expanded }, setExpanded] = useReducer(
resolveChangesReducer,
initialState
)
...
// 👇 value returned by the custom hook
const value = useMemo(
() => ({
expanded,
toggle,
getTogglerProps,
reset,
resetDep: resetRef.current
}),
[expanded, toggle, getTogglerProps, reset]
)
return value
}
// 👇 the available action types
useExpanded.types = {
toggleExpand: 'EXPAND',
reset: 'RESET'
}
With the pattern now implemented internally, here’s how the hacker took
advantage to implement their feature.
useEffectAfterMount(
() => {
// open secret in new tab 👇
window.open('https://leanpub.com/reintroducing-react', '_blank')
//after viewing secret, hacker sets the ref value to true.
hasViewedSecret.current = true
},
[resetDep]
)
...
}
.Expandable-panel {
margin: 0;
padding: 1em 1.5em;
border: 1px solid hsl(216, 94%, 94%);;
min-height: 150px;
}
Quick Quiz! #
Let’s take a quiz.
COMPLETED 0%
1 of 2
Now that we understand the meat of this pattern, there are a couple of things
that we need to work on to clean the code up. See you in the next lesson!
Updating Internal State
Let's see how a user could choose to dictate how internal state is updated.
There are many different ways the user could choose to dictate how internal
state is updated.
For example, the hacker could be more explicit about making their own state
changes only when the action type refers to a toggle expand.
// user's app
...
function appReducer (currentInternalState, action) {
if (
hasViewedSecret.current &&
// look here 👇
action.type === useExpanded.types.toggleExpand
) {
return {
...action.internalChanges,
// 👇 override internal update
expanded: false
}
}
return action.internalChanges
}
...
// useExpanded.js
...
// exposed callback 👇
const override = useCallback(
() => setExpanded({ type: useExpanded.types.override }),
[]
)
...
useExpanded.types = {
toggleExpand: 'EXPAND',
reset: 'RESET',
override: 'OVERRIDE' // 👈 new "OVERRIDE" type
}
The override type is then handled via our internal reducer as follows:
...
case useExpanded.types.override:
return {
...state,
expanded: !state.expanded // update state
}
...
What this allows for is a way to override changes. For example, the hacker
may decide to show a button a user may click in order to view the secret
again.
...
// button is only shown if reader has viewed secret
{ hasViewedSecret.current && (
<button onClick={override}>
Be redeemed to view secret again
</button>
)}
Note how the button’s callback function is override .
This allows for the expanded container to be open — and letting the user view
the secret one more time.
.Expandable-panel {
margin: 0;
padding: 1em 1.5em;
border: 1px solid hsl(216, 94%, 94%);;
min-height: 150px;
}
Quick Quiz! #
Let’s take a quiz.
Q
What does the override functionality allow us to do?
COMPLETED 0%
1 of 1
We’re almost done with the course! Let’s have a look at some quick cleanups
in the next lesson and then we’re done!
Cleanups
You nally made it! We're almost done. Let's clean a few things up.
Try running this below to see how not passing a reducer breaks the app.
.Expandable-panel {
margin: 0;
padding: 1em 1.5em;
border: 1px solid hsl(216, 94%, 94%);;
min-height: 150px;
}
Running the code above will get you an error like this:
Providing a Default Reducer #
Let’s provide a default reducer within our custom hook for users who don’t
need this feature.
// userExpanded.js
function useExpanded (
initialExpanded = false,
userReducer = (s, a) => a.internalChanges // 👈 default
) {
...
}
Using the ES6 default parameters a default reducer has been provided. The
default user reducer takes in state and action, then returns our
internalChanges .
With this fix, users who don’t need the reducer feature don’t get an error.
Remember, the benefit of the state reducer pattern is the fact that it allows the
user “control” over how internal state updates are made.
Our implementation has made our custom hook so much more reusable.
However, how we update state is now part of the exposed API and if you make
changes to that, it may be a breaking change for the user.
Regardless, this is still such a good pattern for complex hooks and
components.
Quick Quiz! #
1
Why have we provided a default user reducer?
COMPLETED 0%
1 of 2
We’ve finally reached the end of this course! Congrats on making it this far 😃
Where to Go from Here?
Want to learn more about web development and React? I have a few other courses already on Educative! Have a
look.
The 4 (Practical) Flexbox Tricks you Need to Know — If you’ve been trying
to get a grip on Flexbox - here’s an interactive practical tutorial for you. No
fluff, just the important practical tricks you need to know.
Last Words
Thank you for taking this course! Good luck in your future React endeavors!
• Last Words
This has been a lengthy discourse on Advanced React Patterns with Hooks. If
you don’t get all of it yet, spend a little more time practicing these patterns in
your day to day work, and I’m pretty sure you’ll get a hang of it real quick.
When you do, go be a React engineer building highly reusable components
with advanced hook patterns.
Last Words #
Thank you for joining me on this journey on teaching Advanced React
Patterns with Hooks. As I sit in my room typing these last words with eyes
pale from working since midnight, I do hope that this course has helped you
learn one or two things. Heck! more than two things, I’d say.
Cheers,
- Ohans Emmanuel