What is a HOC, you might wonder. In plain word, it’s just a function that takes a component and returns a new component. Its pattern looks like below:
const WrapperComponent = (WrappedComponent) => {
//do something
return UpdatedComponent
}
So before we dive into the step by step walkthrough, I just want to highlight that a HOC should be a pure function with no side-effects. This means that it should not make any modifications and just construct the original component by wrapping it in another component.
Also, before create a HOC, we may want to think why do we want to have a HOC? Well, it’s for reusing component logic. It’s also a good way to separate concerns. Let’s say you need to call an API to fetch some data and display the data, but you might want to separate the logic to fetch data and the logic to display data. And you can put the data fetching into a HOC component where it pass down data as well as error status as props to data display layer, so the latter do not need to know about how data is fetched, and just focus on displaying the data based on error status.
Using this example, we can have a step by step walkthrough.
Step 1. Create a normal component handles data fetching logic
class WithDataFetching extends React.Component {
constructor() {
super();
this.state = {
result: [],
error: “”
};
}
async fetchData() {
try {
const res = await fetch("http://randomdatasource.com")
const data = await res.json();
if (data) {
this.setState({
result: data,
});
}
} catch (error) {
this.setState({
error: error.message
});
}
}
async componentDidMount() {
this.fetchData();
}
render() {
//not to return anything yet
}
};
Step 2. Wrap the component we just created in another functional component that simply return it
const withDataFetching = () => {
class WithDataFetching extends React.Component {
constructor() {
super();
this.state = {
result: [],
error: “”
};
}
async fetchData() {
try {
const res = await fetch("http://randomdatasource.com")
const data = await res.json();
if (data) {
this.setState({
result: data,
});
}
} catch (error) {
this.setState({
error: error.message
});
}
}
async componentDidMount() {
this.fetchData();
}
render() {
//not to return anything yet
}
};
return WithDataFetching;
}
Step 3. Add Data Display component as props to the Wrapper component and return it in the render method
const withDataFetching = (<WrappedComponent/>) => {
class WithDataFetching extends React.Component {
constructor() {
super();
this.state = {
result: [],
error: “”
};
}
async fetchData() {
try {
const res = await fetch("http://randomdatasource.com")
const data = await res.json();
if (data) {
this.setState({
result: data,
});
}
} catch (error) {
this.setState({
error: error.message
});
}
}
async componentDidMount() {
this.fetchData();
}
render() {
return <WrappedComponent/>
}
};
return WithDataFetching;
}
Step 4. Pass down states as props to the wrapped component
const withDataFetching = (<WrappedComponent/>) => {
class WithDataFetching extends React.Component {
constructor() {
super();
this.state = {
result: [],
error: “”
};
}
async fetchData() {
try {
const res = await fetch("http://randomdatasource.com")
const data = await res.json();
if (data) {
this.setState({
result: data,
});
}
} catch (error) {
this.setState({
error: error.message
});
}
}
async componentDidMount() {
this.fetchData();
}
render() {
const { results, error } = this.state;
return (
<WrappedComponent
results={results}
error={error}
{…this.props}
/>
);
}
};
return WithDataFetching;
}
Step 5. Pass down any other props as curry function to the wrapper component
const withDataFetching = props => WrappedComponent => {
class WithDataFetching extends React.Component {
constructor() {
super();
this.state = {
results: [],
error: “”
};
}
async fetchData() {
try {
const data = await fetch(props.url)
const json = await data.json();
if (json) {
this.setState({
results: json,
});
}
} catch (error) {
this.setState({
error: error.message
});
}
}
async componentDidMount() {
this.fetchData();
}
render() {
const { results, error } = this.state;
return (
<WrappedComponent
results={results}
error={error}
{…this.props}
/>
);
}
}
return WithDataFetching;
};
export default withDataFetching;
Step 6. Call our Data Fetching HOC and pass in Data Display component as well as API url
function DisplayData({results, error }) {
if (error) {
return "An error has occurred...";
} return (
<>
{results.map((key, result) => (
<div key={id}>{result} </div>
))}
</>
);
}export default withDataFetching({
url: "https://randomdatasource.com"
})(DisplayData);