Hooks Supremacy in React

Himanshu Singh
5 min readMar 30, 2024

Hello there, đź‘‹
I am Himanshu Singh.
Recently, I was reading about React code design patterns, and I read about Compound, High Order Component, Container/Presentational, and Render Props patterns. But one thing, that was common for most patterns is that they can be replaced by new React Hooks now.

But, first, I should discuss all these patterns before we discuss how most of them can be replaced by hooks now.

Let’s start.

Compound Pattern

This pattern is quite useful when you have components that work together with a shared state and logic.

Suppose you are making a Menu component. We have a toggle button that the user can click, and we open a menu list when the toggle is on.

Let’s first create all the required components.
Let’s start with the menu list component.


function List({ children }: ChildrenProp) {
const { open } = useContext(MenuContext);
return open ? <ul>{children}</ul> : null;
}

function Item({ children }: ChildrenProp) {
return <li>{children}</li>;
}

Now, let’s create the toggle button.

function Toggle() {
const { open, toggle } = useContext(MenuContext);
return <button onClick={() => toggle(!open)}>Icon</button>;
}

Now, let’s create our main Menu component.

const MenuContext = createContext<MenuContextValue>({
open: false,
toggle: () => {},
});

function Menu({ children }: ChildrenProp) {
const [open, toggle] = useState(false);
return (
<MenuContext.Provider value={{ open, toggle }}>
{children}
</MenuContext.Provider>
);
}

…

Menu.Toggle = Toggle;
Menu.List = List;
Menu.Item = Item;

Now, whenever you need to use a Menu component, you will have to import only one Menu component and then you can use all the properties on that only.

<Menu>
<Menu.Toggle />
<Menu.List>
<Menu.Item>Item One</Menu.Item>
<Menu.Item>Item Two</Menu.Item>
</Menu.List>
</Menu>

You can find the code here.

HOC Pattern

When some stateful logic is repeated in many components in our project, then we can put that code in one place, and with the use of a high-order component pattern, we can pass it to other components.
This pattern helps in code maintainability as if in the future we want to make some changes in that logic, we will have to make them in only one place. So, It helps in keeping our code DRY (Don’t Repeat Yourself).

Let’s take an example, suppose we want to render a list of dog images and we want to put the image fetching logic in one place so we can use it for other components if needed.

function withLoader(
Component: ({ data }: ComponentProps) => JSX.Element,
url: string
) {
return function (props: Record<string, unknown>) {
const [data, setData] = useState<ComponentProps['data'] | null>(null);

useEffect(() => {
(async function fetchDogImages() {
try {
const response = await fetch(url);
const data = await response.json();
setData(data);
} catch (error) {
console.log('Fetch Dog Images Error - ', error);
}
})();
}, [url]);

if (data) {
return <Component {…props} data={data} />;
}

return <div>Loading…</div>;
};
}

And, also let’s create the DogImages Component.

function DogImages({ data }: ComponentProps) {
return (
<>
{data.message.map(function DogImage(dog: string, index: number) {
return <img src={dog} alt="Dog" key={index} />;
})}
</>
);
}

const DogImagesWithLoader = withLoader(
DogImages,
'https://dog.ceo/api/breed/labrador/images/random/6'
);

Now, we can pass any component, and URL in withLoader HOC to fetch the data and to show Loading when it is getting fetched.

There are two issues that you can find in this pattern. The first is props name collision and the second is a nested component list when you compose multiple HOCs together.

You can find the code here.

Container/Presentational Pattern

This pattern is quite useful, as it helps in the separation of concerns. We have a container component that cares about what data is shown to the user, and we have a presentational component that cares about how data is shown to the user.

The presentational component receives the data through props and renders it to show the user without modifying it. They are usually stateless: they do not contain their state unless they need it for UI purposes.

Let’s create a presentational DogImages component.

function DogImages({ dogs }: DogImagesProps) {
return (
<>
{dogs.map(function DogImage(dog, index) {
return <img src={dog} alt="Dog" key={index} />;
})}
</>
);
}

The primary function of the container component is to pass data to the presentational component it contains.
Let’s create a DogImagesContainer component.

function DogImagesContainer() {
const [dogs, setDogs] = useState([]);

useEffect(() => {
(async function fetchDogs() {
try {
const response = await fetch(
'https://dog.ceo/api/breed/labrador/images/random/6'
);
const data = await response.json();
setDogs(data.message);
} catch (error) {
console.log('DogImagesContainer - Error', error);
}
})();
}, []);

return <DogImages dogs={dogs} />;
}

This pattern has some good pros. Presentational components can be easily made reusable, as they only render data. Anyone can make changes in the presentational components, as it only has code for how data is shown to the user.

You can find the code here.

Render Props Pattern

This is another pattern (HOC is the other one) to make a component logic reusable. We can either use a render prop or children as a function to use the logic of some other component.

Suppose we want to build an app where the user enters a temperature in Celsius and we convert it into Kelvin and Fahrenheit and show it to the user.

Let’s first create all the required components.

function Input({ children }: { children: (value: string) => JSX.Element }) {
const [value, setValue] = useState('');

return (
<>
<input
type="text"
placeholder="temp in °C"
value={value}
onChange={(e) => setValue(e.target.value)}
/>
{children(value)}
</>
);
}

function Kelvin({ value }: { value: string }) {
return <div>{Number(value || 0) + 273.15}K</div>;
}

function Fahrenheit({ value }: { value: string }) {
return <div>{(Number(value || 0) * 9) / 5 + 32}F</div>;
}

Now, to use the entered value in Kelvin and Fahrenheit component. We can use children as a function.

function App() {
return (
<Input>
{(value) => (
<>
<Kelvin value={value} />
<Fahrenheit value={value} />
</>
)}
</Input>
);
}

You can also use a render prop in the Input component to do the same thing. And, one benefit of this over HOC is that it does not have a naming collision issue.

You can find the code here.

If you check the HOC pattern and Container/Presentational pattern, you can move the data fetching logic in a custom hook and can share the same logic throughout the application without repeating it.
And many times, depending on the context, you can also use hooks for Render Props.

That’s all

Thank you for reading it till the end. I hope you learned something from it. I was reading React design patterns on https://www.patterns.dev/react and got the idea to write it from there.

Connect with me on Twitter and GitHub.

--

--

Himanshu Singh

I write blogs around React JS, JavaScript, Web Dev, and Programming. Follow to read blogs around them.