Building the Compound Child Components
Now, let’s work on the Expandable component's child components and actually see some output!
We'll cover the following
Child Components of Expandable
#
There are three child components for the Expandable
component.
These child components need to consume values from the context object created in Expandable.js
.
To make this possible, we’ll do a little refactoring as shown below:
import React, { createContext, useState, useCallback, useRef, useEffect, useMemo } from 'react'export const ExpandableContext = createContext()const { Provider } = ExpandableContextconst Expandable = ({ children, onExpand }) => {const [expanded, setExpanded] = useState(false)const toggle = useCallback(() => setExpanded(prevExpanded => !prevExpanded),[])const componentJustMounted = useRef(true)useEffect(() => {if (!componentJustMounted.current) {onExpand(expanded)}componentJustMounted.current = false},[expanded])const value = useMemo(() => ({ expanded, toggle }),[expanded, toggle])return (<Provider value={value}>{children}</Provider>)}export default Expandable
We export the context object, ExpandableContext
, from Expandable.js
.
Now, we may use the useContext
hook to consume the values from the Provider
.
The Header
Child #
Below is the Header
child component fully implemented.
//Header.jsimport React, { useContext } from 'react'import { ExpandableContext } from './Expandable'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.
The Body
Child #
Here’s the implementation for the Body
child component:
// Body.jsimport { useContext } from 'react'import { ExpandableContext } from './Expandable'const Body = ({ children }) => {const { expanded } = useContext(ExpandableContext)return expanded ? children : null}export default Body
Pretty simple as well.
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.
The Icon
Child #
The Icon
component is just as simple.
// Icon.jsimport { useContext } from 'react'import { ExpandableContext } from './Expandable'const Icon = () => {const { expanded } = useContext(ExpandableContext)return expanded ? '-' : '+'}export default Icon
It renders either +
or -
depending on the value of expanded
retrieved from the context object.
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'...const Expandable = ({ children, onExpand }) => {...}// Remember this is just a personal reference. It's not mandatoryExpandable.Header = HeaderExpandable.Body = BodyExpandable.Icon = Icon
Using the Expandable
Component #
Now, we can go ahead and use the Expandable
component as designed:
<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' const Body = ({ children }) => { const { expanded } = useContext(ExpandableContext) return expanded ? children : null } export default Body
Quick Quiz! #
Select all that apply for this question.
Why do we export the ExpandableContext
from Expandable.js
?
The child components of Expandable
need to consume values from the context object created in Expandable.js
.
Because the user’s app needs to use the values in that context
The context cannot be used elsewhere without the export
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!