Passing Props from HOC to Children
Passing props to children of a Higher Order component in React
Published: 22 June 2020
HOC in React
Higher Order Components (HOC) in React are special components which take in other components and tranform them into new components.
For practical use, we can think of HOCs as generalised components which can do common tasks for other components.
Simple HOC
Let's say we have various components whose header will always be the same. We can create an HOC which will take in a given component, attach a header to it and return a new component.
//SimpleHOC.js
const SimpleHOC = props => {
return (
<div className="hoc">
<header>
<h1>My Beautiful Heading</h1>
</header>
<>{props.children}</>
</div>
)
}
Now, let's use this HOC component and pass a component to it, which will be available as props.children inside the HOC.
// RandomComponent.js
import SimpleHOC from "./SimpleHOC.js"
const RandomComponent = props => {
return (
<SimpleHOC>
<div className="bodyText">
<p>This is some random body text</p>
</div>
</SimpleHOC>
)
}
The above div with class bodyText, will be treated as props.children inside the HOC. The final Pseudo resulting HTML will look like so:
<div className="hoc">
<header>
<h1>My Beautiful Heading</h1>
</header>
<div className="bodyText">
<p>This is some random body text</p>
</div>
</div>
Passing Props
Sometimes, rather than just rendering the children as is, the HOC may want to pass some data to them as well. Let's say we have components which need to make GET request to external API endpoints and render the returning response.
//ComplexHOC.js
const ComplexHOC = props => {
const [apiResponse, setApiResponse] = useState(null)
// function for calling external API
const makeApiCall = async url => {
const resp = await fetch(url)
const data = await resp.json()
setApiResponse(data)
}
useEffect(() => {
makeApiCall(props.url)
}, [props.url])
// we need to pass apiResponse to props.children
return <div className="hoc">{props.children}</div>
}
Approach 1 - React.cloneElement
Under this approach, we will:
(1) Create a clone of the children
(2) Pass whatever data we want from HOC to the children
//ComplexHOC.js
const ComplexHOC = props => {
const [apiResponse, setApiResponse] = useState(null)
...
// create clone and pass it the needed data
const ClonedChildren = React.cloneElement(props.children,{apiResponse});
return <div className="hoc">{ClonedChildren}</div>
}
Let's create the children for HOC:
// RandomComponent.js
import ComplexHOC from "./ComplexHOC.js"
const RandomComponent = props => {
return (
<ComplexHOC url="https://jsonplaceholder.typicode.com/todos/1">
<div className="bodyText">
<p>This is some random todo text - {props.title}</p>
</div>
</ComplexHOC>
)
}
This will throw an error with React complaining that it can't find props.title, but why?
We are passing a simple div to the HOC. Div, span, p, h1 or any other usual DOM elements are only allowed to accept their usual properties like id, className, role, etc, which makes sense. They can't be given custom props unless we use data-attributes.
What react needs is a React component to be passed in as children like so:
// RandomComponent.js
import ComplexHOC from "./ComplexHOC.js"
// create a wrapper component so encapsulate all the HTML
const WrapperComp = props => {
return (
<div className="bodyText">
{props.apiResponse && (
<p>
UserID - {props.apiResponse.userId} & Title -{props.apiResponse.title}
</p>
)}
</div>
)
}
// now pass it inside ComplexHOC as child
const RandomComponent = props => {
return (
<ComplexHOC url="https://jsonplaceholder.typicode.com/todos/1">
<WrapperComp />
</ComplexHOC>
)
}
This will work since we have now created a React Element which is configured to use props and handle any data as such.
Approach 2 - Context
Another approach is to use React's context API which is helpful in maintaining global states for our applications as well as passing props around.
Let's adapt our HOC component to use this:
//ComplexHOC.js
export const ApiContext = React.createContext();
const ComplexHOC = props => {
const [apiResponse, setApiResponse] = useState(null)
...
return <ApiContext.Provider value={apiResponse}>{props.children}</ApiContext.Provider>;
}
This provider component will make value prop available to all children who use ComplexHOC like so:
const WrapperComp = () => {
const data = useContext(ApiContext)
console.log("context data", data)
return (
<div className="bodyText">
{data && (
<p>
UserID - {data.userId} & Title - {data.title}
</p>
)}
</div>
)
}
const SingleTodo = () => {
return (
<ComplexHOC url="https://jsonplaceholder.typicode.com/todos/1">
<WrapperComp />
</ComplexHOC>
)
}
We have again used the wrapper component as React element to pass to HOC. This time though, we extract data using useContext hook and pass it down to our elements.