Higher Order Components
Higher order components are the javascript functions that takes a component as an input, modifies it or add some features in it and return it as a component. It’s a function that takes a component as an input and returns a new component with additional functionality.
const withExtraFunctionality = (OriginalComponent) => {
return (props) => {
// You can modify props, add logic, or even add new UI here
return <OriginalComponent {...props} />;
};
};
Why to use HOCs ?
Code Reusability: HOCs help avoid duplicating logic across components. For example, if multiple components need authentication logic, an HOC can handle that.
Separation of Concerns: HOCs separate behavior (logic) from the presentation layer (UI components).
Decorating Components: You can use HOCs to enhance components with additional props, event listeners, or side effects.
Common use cases of HOCs
Authentication: Wrap components with logic to check if the user is authenticated.
Fetching Data: Fetch data and pass it to the wrapped component as props.
Conditional Rendering: Render components based on certain conditions.
Creating a HOC
import React from 'react';
const withLoader = (WrappedComponent) => {
return ({ isLoading, ...props }) => {
if (isLoading) {
return <div>Loading...</div>;
}
return <WrappedComponent {...props} />;
};
};
export default withLoader;
Using a HOC
import React from 'react';
import withLoader from './withLoader';
function DataDisplay({ data }) {
return (
<div>
<h1>Data:</h1>
<pre>{JSON.stringify(data, null, 2)}</pre>
</div>
);
}
const DataDisplayWithLoader = withLoader(DataDisplay);
// Example Usage
function App() {
const data = { name: 'John', age: 30 };
return <DataDisplayWithLoader isLoading={false} data={data} />;
}
export default App;
In this example:
The withLoader HOC adds a “Loading…” message if the isLoading prop is true.
Otherwise, it renders the original DataDisplay component.
Some points to note
Avoid Over-Nesting: Wrapping components with multiple HOCs can lead to deeply nested component trees, making debugging harder.
Name the HOC Clearly: Always give your HOCs meaningful names to describe their purpose (e.g.,
withAuth
,withErrorBoundary
).Pass Props Correctly: Use the spread operator
{...props
} to ensure the wrapped component receives all necessary props.Overhead: HOCs can introduce additional re-renders if not implemented efficiently.
Complexity: They can make the component structure harder to understand, especially if multiple HOCs are nested.
Lifting State Up
Sometimes, you want the state of two components to always change together. To do it, remove state from both of them, move it to their closest common parent, and then pass it down to them via props. This is known as lifting state up, and it’s one of the most common things you will do writing React code.
Lifting State Up refers to the process of moving a shared state to the closest common ancestor of components that need access to it. This helps synchronize state between child components by maintaining it in one place, allowing those child components to share the same source of truth.
Example
In this example, a parent Accordion
component renders two separate Panel
.
import { useState } from 'react';
function Panel({ title, children }) {
const [isActive, setIsActive] = useState(false);
return (
<section className="panel">
<h3>{title}</h3>
{isActive ? (
<p>{children}</p>
) : (
<button onClick={() => setIsActive(true)}>
Show
</button>
)}
</section>
);
}
export default function Accordion() {
return (
<>
<h2>Accordion</h2>
<Panel title="First">
This is the first panel
</Panel>
<Panel title="Second">
This is the second panel
</Panel>
</>
);
}
Each Panel
component has a boolean isActive
state that determines whether its content is visible. As both panels have their own states, they are independent of each other.
But now let’s say you want to change it so that only one panel is expanded at any given time. With that design, expanding the second panel should collapse the first one.
To coordinate these two panels, you need to “lift their state up” to a parent component in three steps:
Step 1: Remove state from the child components
You will give control of the Panel
’s isActive
to its parent component. This means that the parent component will pass isActive
to Panel
as a prop instead. Start by removing this line from the Panel
component:
const [isActive, setIsActive] = useState(false);
And instead, add isActive
to the Panel
’s list of props:
function Panel({ title, children, isActive }) {
Now the Panel
’s parent component can control isActive
by passing it down as a prop. Conversely, the Panel
component now has no control over the value of isActive
—it’s now up to the parent component!
Step 2: Pass hardcoded data from the common parent
To lift state up, you must locate the closest common parent component of both of the child components that you want to coordinate (Accordion in our case):
import { useState } from 'react';
export default function Accordion() {
return (
<>
<h2>Accordion</h2>
<Panel title="First" isActive={true}>
This is the first panel
</Panel>
<Panel title="Second" isActive={true}>
This is the second panel
</Panel>
</>
);
}
function Panel({ title, children, isActive }) {
return (
<section className="panel">
<h3>{title}</h3>
{isActive ? (
<p>{children}</p>
) : (
<button onClick={() => setIsActive(true)}>
Show
</button>
)}
</section>
);
}
Step 3: Add state to the common parent
Lifting state up often changes the nature of what you’re storing as state.
In this case, only one panel should be active at a time. This means that the Accordion
common parent component needs to keep track of which panel is the active one. Instead of a boolean
value, it could use a number as the index of the active Panel
for the state variable:
const [activeIndex, setActiveIndex] = useState(0);
When the activeIndex
is 0
, the first panel is active, and when it’s 1
, it’s the second one. In case of more panels we can render them using map
function and using their index to set them.
<>
<Panel
isActive={activeIndex === 0}
onShow={() => setActiveIndex(0)}
>
...
</Panel>
<Panel
isActive={activeIndex === 1}
onShow={() => setActiveIndex(1)}
>
...
</Panel>
</>
Clicking the “Show” button in either Panel
needs to change the active index in Accordion
. A Panel
can’t set the activeIndex
state directly because it’s defined inside the Accordion
. The Accordion
component needs to explicitly allow the Panel
component to change its state by passing an event handler down as a prop.
So now our final code after lifting up the state will be:
import { useState } from 'react';
export default function Accordion() {
const [activeIndex, setActiveIndex] = useState(0);
return (
<>
<h2>Almaty, Kazakhstan</h2>
<Panel
title="About"
isActive={activeIndex === 0}
onShow={() => setActiveIndex(0)}
>
With a population of about 2 million, Almaty is Kazakhstan's largest city. From 1929 to 1997, it was its capital city.
</Panel>
<Panel
title="Etymology"
isActive={activeIndex === 1}
onShow={() => setActiveIndex(1)}
>
The name comes from <span lang="kk-KZ">алма</span>, the Kazakh word for "apple" and is often translated as "full of apples". In fact, the region surrounding Almaty is thought to be the ancestral home of the apple, and the wild <i lang="la">Malus sieversii</i> is considered a likely candidate for the ancestor of the modern domestic apple.
</Panel>
</>
);
}
function Panel({
title,
children,
isActive,
onShow
}) {
return (
<section className="panel">
<h3>{title}</h3>
{isActive ? (
<p>{children}</p>
) : (
<button onClick={onShow}>
Show
</button>
)}
</section>
);
}
What are Controlled and Uncontrolled Components ?
Uncontrolled Components are the ones with some local state of their own which means that they cannot be controlled or influenced by their parent. Whereas Controlled Components are the ones which can be controlled by their closest parent using props instead of their own local states.This lets the parent component fully specify its behavior. Uncontrolled Components are easier to use within their parents because they require less configuration. But they’re less flexible when you want to coordinate them together. Controlled Components are maximally flexible, but they require the parent components to fully configure them with props.
Prop Drilling
Prop Drilling is a process in React where you pass data (or functions) from a parent component to deeply nested child components by passing them down through intermediate components as props. Even if the intermediate components don’t need the data themselves, they still pass it along, which can make the code harder to maintain and understand.
Let’s say you have a deeply nested component tree, and the deepest component needs access to a piece of state from the parent.
function App() {
const [username, setUsername] = React.useState("JohnDoe");
return <Parent username={username} />;
}
function Parent({ username }) {
return <Child username={username} />;
}
function Child({ username }) {
return <Grandchild username={username} />;
}
function Grandchild({ username }) {
return <p>Welcome, {username}!</p>;
}
In this example, to get the username to the Grandchild component, it must be passed as a prop through the Parent and Child components, even though they don’t use it.
Problems with Prop Drilling
Cluttered Code: The intermediate components (Parent and Child) become cluttered with props they don’t use.
Tight Coupling: Changes to the data structure or component hierarchy require updating the props being passed through the entire chain.
Scalability Issues: As the app grows, managing deeply nested prop chains can become cumbersome.
Solutions to avoid Prop Drilling
Context API
State Management Libraries like Redux or Zustand.
Context API
In React, data is passed from parent to child components via props. While this is fine for simple applications, it can lead to prop drilling in more complex component hierarchies—where props are passed through multiple intermediate components that don’t directly use the data. The Context API is a built-in feature of React that solves this by enabling global state management for components that need it, bypassing intermediate ones.
How does it work ?
The Context API is built around three main components:
Context Provider: Supplies the context data to all children components.
Context Consumer: Retrieves and uses the data from the context.
useContext Hook: Simplifies context consumption for functional components. (Cannot be used in class based components).
Let’s try using it for a Theme Toggle
Step 1: Creating a Context
import React, { createContext, useContext } from "react";
// Create a Context with a default value
const ThemeContext = createContext("light");
function ThemeSwitcher() {
// Access the context value using useContext in same or different file
const theme = useContext(ThemeContext);
return <p>Current Theme: {theme}</p>;
}
function App() {
// No Provider is used here
return (
<div>
<h1>Welcome to the App</h1>
<ThemeSwitcher />
</div>
);
}
export default App;
How This Works:
Default Value: The
createContext("light")
defines "light" as the default value of the context. This value is returned when no Provider is found in the component tree.useContext: The ThemeSwitcher component directly accesses the
ThemeContext
usinguseContext
. Since no Provider is wrapping the component, it falls back to the default value.
Limitations of Using Context Without Provider:
No Dynamic Updates: The default value cannot change dynamically because there’s no Provider to supply updated values.
Less Flexibility: In real-world applications, you often need dynamic data (e.g., toggling themes), which requires a Provider.
Global Behavior: All components using the context will share the same default value, limiting customization.
Creating Context with Provider
import React, { createContext, useState } from "react";
// Create a context
const ThemeContext = createContext();
Step 2: Provide Context to the Component Tree
Wrap your application or specific components with the ThemeContext.Provider to supply data.
function ThemeProvider({ children }) {
const [theme, setTheme] = useState("light");
const toggleTheme = () => {
setTheme((prevTheme) => (prevTheme === "light" ? "dark" : "light"));
};
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
}
Step 3: Consume Context in a Component
Use the useContext
hook to consume the context in child components.
import React, { useContext } from "react";
function ThemeSwitcher() {
const { theme, toggleTheme } = useContext(ThemeContext);
return (
<div>
<p>Current Theme: {theme}</p>
<button onClick={toggleTheme}>Toggle Theme</button>
</div>
);
}
function App() {
return (
<ThemeProvider>
<ThemeSwitcher />
</ThemeProvider>
);
}
export default App;
In this way, we can consume context using useContext
Hook. But it is only possible in functional components as class based components does not support React Hooks. So we can use Context Consumer for class based components.
import React, { Component } from "react";
import ThemeContext from "./ThemeContext";
class Grandchild extends Component {
render() {
return (
<ThemeContext.Consumer>
{(theme) => (
<p>The current theme is: {theme}</p>
)} {/* Theme value is provided by the nearest Provider */}
</ThemeContext.Consumer>
);
}
}
export default Grandchild;
How Context.Consumer Works ?
The Consumer component takes a render prop (a function) as its child.
This function receives the current context value (from the nearest Provider or the default value) as its argument.
The function returns the JSX to be rendered.
Note: If Context Providers are nested then at children elements will consume the context of the closest parent provider.
This was one way of avoiding prop drilling. In the next article, we’ll study about Redux State Management Library.