0% found this document useful (0 votes)
7 views14 pages

Advanced React c7

Chapter 7 discusses higher-order components (HOCs) in React, which are functions that take a component as an argument, enhance it with additional logic, and return a new component. While hooks have largely replaced HOCs for sharing stateful logic, HOCs remain useful for enhancing callbacks, lifecycle events, and intercepting DOM events. The chapter provides examples of creating HOCs for logging events and managing keyboard interactions, emphasizing their utility in modern React development.

Uploaded by

Bora
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
7 views14 pages

Advanced React c7

Chapter 7 discusses higher-order components (HOCs) in React, which are functions that take a component as an argument, enhance it with additional logic, and return a new component. While hooks have largely replaced HOCs for sharing stateful logic, HOCs remain useful for enhancing callbacks, lifecycle events, and intercepting DOM events. The chapter provides examples of creating HOCs for logging events and managing keyboard interactions, emphasizing their utility in modern React development.

Uploaded by

Bora
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

Chapter 7.

Higher-order
components in modern world

There is one final composition technique that we need to discuss before


moving on to other parts of React: higher-order components! Before
hooks took over, this was one of the most popular patterns for sharing
stateful logic and context data. It's still used here and there even today,
especially in older libraries or in projects that started their life before
hooks. So while it's probably not the best idea to introduce them in new
code, understanding what they are and how they work is still more or
less mandatory.

So let's start from the beginning and learn in the process:

What is a higher-order component pattern?


How can we use higher-order components to enhance callbacks
and React lifecycle events?
Different ways to pass data to higher-order components.
How to create reusable components that intercept DOM and
keyboard events.

What is a higher-order component?


According to the React docs, a Higher-Order Component[7] is an
advanced technique for reusing component logic that is used for cross-
cutting concerns.

In English, it's a function that accepts a component as one of its


arguments, executes some logic, and then returns another component

Page 126
that renders the component from the argument. The simplest variant of
it, that does nothing, is this:

// accept a Component as an argument


const withSomeLogic = (Component) => {
// do something

// return a component that renders the component from the


argument
return (props) => <Component {...props} />;
};

The key here is the return part of the function - it's just a component,
like any other component.

And then, when it's time to use it, it would look like this:

// just a button
const Button = ({ onClick }) => (
<button onClick={onClick}>Button</button>
);

// same button, but with enhanced functionality


const ButtonWithSomeLogic = withSomeLogic(Button);

You pass your Button component to the function, and it returns the
new Button , which includes whatever logic is defined in the higher-
order component. And then this button can be used like any other
button:

const SomePage = () => {


return (
<>
<Button />
<ButtonWithSomeLogic />
</>

Page 127
);
};

The simplest and most common use case would be to inject props into
components. We can, for example, implement a withTheming
component that extracts the current theme of the website (dark or light
mode) and sends that value into the theme prop. It would look like
this:

const withTheme = (Component) => {


// isDark will come from something like context
const theme = isDark ? 'dark' : 'light';

// making sure that we pass all props to the component back


// and also inject the new one: theme
return (props) => <Component {...props} theme={theme} />;
};

And now, if we use it on our button, it will have the theme prop
available for use:

const Button = ({ theme }) => {


// theme prop here will come from withTheme HOC below
return <button className={theme} ...>Button</button>
}

const ButtonWithTheme = withTheme(Button);

Interactive example and full code


[Link]

Before the introduction of hooks, higher-order components were widely


used for accessing context and any external data subscriptions. Redux's
old connect [8] or React Router's withRouter [9] functions are

Page 128
higher-order components: they accept a component, inject some props
into it, and return it back.

As you can see, higher-order components are quite complicated to write


and understand. So when hooks were introduced, it's no wonder
everyone switched to them.

Now, instead of creating complicated mental maps of which prop goes


where and trying to figure out how theme ended up in props, we can
just write:

const Button = () => {


// we see immediately where the theme is coming from
const { theme } = useTheme();

return <button appearance={theme} ...>Button</button>


};

Everything that is happening in the component can be read from top to


bottom, and the source of all the data is obvious, which significantly
simplifies debugging and development.

And while hooks have probably replaced 99% of shared logic concerns
and 100% of use cases for accessing context, higher-order components
can still be useful even in modern code. Mostly for enhancing callbacks,
React lifecycle events, and intercepting DOM and keyboard events. Only
if you're feeling fancy, of course. Those use cases can also be
implemented with hooks, just not as elegantly.

Let's take a look at them.

Enhancing callbacks
Imagine you need to send some sort of advanced logging on some
callbacks. When you click a button, for example, you want to send some

Page 129
logging events with some data. How would you do it with hooks? You'd
probably have a Button component with an onClick callback:

const Button = ({ onClick, children }) => {


return <button onClick={onClick}>{children}</button>;
};

And then on the consumer side, you'd hook into that callback and send
logging events there:

const SomePage = () => {


const log = useLoggingSystem();

const onClick = () => {


log('Button was clicked');
};

return <Button onClick={onClick}>Click here</Button>;


};

And that is fine if you want to fire an event or two. But what if you want
your logging events to be consistently fired across your entire app
whenever the button is clicked? We probably can bake it into the
Button component itself:

const Button = ({ onClick }) => {


const log = useLoggingSystem();

const onButtonClick = () => {


log('Button was clicked');
onClick();
};

return <button onClick={onButtonClick}>Click me</button>;


};

Page 130
But then what? For proper logs, you'd have to send some sort of data as
well. We surely can extend the Button component with some
loggingData props and pass it down:

const Button = ({ onClick, loggingData }) => {


const onButtonClick = () => {
log('Button was clicked', loggingData);
onClick();
};
return <button onClick={onButtonClick}>Click me</button>;
};

But what if you want to fire the same events when the click has happened
on other components? Button is usually not the only thing people can
click on in our apps. What if I want to add the same logging to a
ListItem component? Copy-paste exactly the same logic there?

const ListItem = ({ onClick, loggingData }) => {


const onListItemClick = () => {
log('List item was clicked', loggingData);
onClick();
};
return <Item onClick={onListItemClick}>Click me</Item>;
};

Too much copy-pasting and prone to errors and someone forgetting to


change something in my taste.

What I want, essentially, is to encapsulate the logic of "something


triggered onClick callback - send some logging events" somewhere
and then just reuse it in any component I want without changing the
code of those components in any way.

This is the first use case where hooks are of no use, but higher-order
components could come in handy.

Page 131
Instead of copy-pasting the "click happened → log data" logic
everywhere, I can create a withLoggingOnClick function that:

Accepts a component as an argument.


Intercepts its onClick callback.
Sends the data that I need to whatever external framework is used
for logging.
Returns the component with the onClick callback intact for
further use.

It would look something like this:

// just a function that accepts Component as an argument


export const withLoggingOnClick = (Component) => {
return (props) => {
const onClick = () => {
[Link]('Log on click something');
// don't forget to call onClick that is coming from props!
// we're overriding it below
[Link]();
};

// return original component with all the props


// and overriding onClick with our own callback
return <Component {...props} onClick={onClick} />;
};
};

Now, I can just add it to any component that I want. I can have a
Button with logging baked in:

export const ButtonWithLoggingOnClick =


withLoggingOnClick(SimpleButton);

Or use it in the list item:

Page 132
export const ListItemWithLoggingOnClick =
withLoggingOnClick(ListItem);

Or any other component that has an onClick callback that I want to


track. Without a single line of code changed in either Button or
ListItem components!

Interactive example and full code


[Link]

Adding data to the higher-order component


Now, what's left to do is to add some data from the outside to the logging
function. Considering that a higher-order component is nothing more
than just a function, we can do that easily. We just need to add some
other arguments to the function, that's it:

export const withLoggingOnClickWithParams = (


Component,
// adding some params as a second argument to the function
params,
) => {
return (props) => {
const onClick = () => {
// accessing params that we passed as an argument here
// everything else stays the same
[Link]('Log on click: ', [Link]);
[Link]();
};

return <Component {...props} onClick={onClick} />;


};
};

Page 133
Now, when we wrap our button with a higher-order component, we can
pass the text that we want to log:

const ButtonWithLoggingOnClickWithParams =
withLoggingOnClickWithParams(SimpleButton, {
text: 'button component',
});

On the consumer side, we'd just use this button as a normal button
component, without worrying about the logging text:

const Page = () => {


return (
<ButtonWithLoggingOnClickWithParams
onClick={onClickCallback}
>
Click me
</ButtonWithLoggingOnClickWithParams>
);
};

But what if we actually want to worry about this text? What if we want
to send different texts in different contexts of where the button is used?
We wouldn't want to create a million wrapped buttons for every use case.

Also very easy to solve: instead of passing that text as a function's


argument, we can just pass it as a prop to the resulting button. The code
would look like this:

<ButtonWithLoggingOnClickWithProps
onClick={onClickCallback}
logText="this is Page button"
>
Click me
</ButtonWithLoggingOnClickWithProps>

Page 134
And then we can just extract that logText from props that were sent
to the button:

export const withLoggingOnClickWithProps = (Component) => {


// it will be in the props here, just extract it
return ({ logText, ...props }) => {
const onClick = () => {
// and then just use it here
[Link]('Log on click: ', logText);
[Link]();
};

return <Component {...props} onClick={onClick} />;


};
};

Interactive example and full code


[Link]

Enhancing React lifecycle events


We are not limited to clicks and callbacks here. Remember, these are just
components, we can do whatever we want and need. We can use
everything React has to offer. For example, we can send those logging
events when a component is mounted:

export const withLoggingOnMount = (Component) => {


return (props) => {
// no more overriding onClick
// use normal useEffect - it's just a component!
useEffect(() => {
[Link]('log on mount');
}, []);

Page 135
// and pass back props intact
return <Component {...props} />;
};
};

or even read props and send them on re-renders, when a certain prop
has changed:

export const withLoggingOnReRender = (Component) => {


return ({ id, ...props }) => {
// fire logging every time "id" prop changes
useEffect(() => {
[Link]('log on id change');
}, [id]);

// and pass back props intact


return <Component {...props} />;
};
};

Interactive example and full code


[Link]

Intercepting DOM events


Another very useful application of higher-order components is
intercepting various DOM and keyboard events. Imagine, for example,
you're implementing some sort of keyboard shortcuts functionality for
your page. When specific keys are pressed, you want to do various
things, like opening dialogs, creating issues, etc. You'd probably add an
event listener to the window for something like this:

useEffect(() => {

Page 136
const keyPressListener = (event) => {
// do stuff
};

[Link]('keypress', keyPressListener);

return () =>
[Link](
'keypress',
keyPressListener,
);
}, []);

And then, you have various parts of your app, like modal dialogs,
dropdown menus, drawers, etc., where you want to block that global
listener while the dialog is open. If it was just one dialog, you could
manually add onKeyPress to the dialog itself and there do
[Link]() for that:

export const Modal = ({ onClose }) => {


const onKeyPress = (event) => [Link]();

return (
<div onKeyPress={onKeyPress}>...// dialog code</div>
);
};

But the same story as with onClick logging - what if you have multiple
components where you want to see this logic? Copy-paste that
[Link] everywhere? Meh.

What we can do instead is, again, implement a higher-order component.


This time it will accept a component, wrap it in a div with onKeyPress
callback attached, and return the component unchanged.

export const withSuppressKeyPress = (Component) => {

Page 137
return (props) => {
const onKeyPress = (event) => {
[Link]();
};

return (
<div onKeyPress={onKeyPress}>
<Component {...props} />
</div>
);
};
};

That is it! Now we can wrap any component in it:

const ModalWithSuppressedKeyPress =
withSuppressKeyPress(Modal);
const DropdownWithSuppressedKeyPress =
withSuppressKeyPress(Dropdown);
// etc

and just use it everywhere:

const Component = () => {


return <ModalWithSuppressedKeyPress />;
};

Now, when this modal is open and focused, any key press event will
bubble up through the elements' hierarchy until it reaches our div in
withSuppressKeyPress that wraps the modal and will stop there.
Mission accomplished, and developers who implement the Modal
component don't even need to know or care about it.

Interactive example and full code


[Link]

Page 138
Key takeaways
That's enough of a history lesson for the book, I think. A few things to
remember before we jump to the next chapter, with the most exciting
and the most controversial part of React: state management!

A higher-order component is just a function that accepts a


component as an argument and returns a new component. That
new component renders the component from the argument.
We can inject props or additional logic into the components that
are wrapped in a higher-order component.

// accept a Component as an argument


const withSomeLogic = (Component) => {
// inject some logic here

// return a component that renders the component from the


argument
// inject some prop to it
return (props) => {
// or inject some logic here
// can use React hooks here, it's just a component

return <Component {...props} some="data" />;


};
};

We can pass additional data to the higher-order component,


either through the function's argument or through props.

Page 139

You might also like