Here is a list of design patterns in React I have picked up over the years working with React based technologies.
High Order Components
These takes a component as a prop, applies some logic and returns the same component with extra properties.
It is used when we have to apply same logic to multiple components.
Example:
// Comp1.tsx
import { forwardRef } from "react";
import WithDimension from "./WithDimension";
const Component = (props, ref) => {
return (
<div ref={ref} className="comp1">
Hey I am comp1 width: {props.width}
</div>
);
};
export default WithDimension(forwardRef(Comp1));
// withDimensions.tsx
import { useEffect, useRef, useState } from "react";
const withDimension = (Element) => {
function WithDimensions(props) {
const compRef = useRef();
const [width, setWidth] = useState(null);
const [height, setHeight] = useState(null);
useEffect(() => {
if (compRef.current) {
setWidth(compRef.current.offsetWidth);
setHeight(compRef.current.offsetHeight);
}
}, [compRef]);
return <Element ref={compRef} width={width} height={height} {...props} />;
}
return WithDimensions;
};
export default withDimension;
Note: To use ref in react component we have to use forwardRef() on the component which is wrapped inside a higher order component.
Render Props Pattern
A prop in component which is a function that returns JSX.
It can be used to make component customizable.
Example:
// App.tsx
import Input from "./Input";
export default function App() {
const showValue = (value) => <b>The value is {value}</b>;
const multiplyByTen = (value) => <>The multiplied value is {value * 10}</>;
return (
<div className="App">
<Input renderTextBelow={showValue} />
<br />
<Input renderTextBelow={multiplyByTen} />
</div>
);
}
// Input.tsx
import { useState } from "react";
const Input = (props) => {
const [value, setValue] = useState(null);
const handleChange = (e) => {
setValue(e.target.value);
};
return (
<>
<input value={value} onChange={handleChange} />
<br />
{props.renderTextBelow(value)}
</>
);
};
export default Input;
Compound Pattern
Multiple components come together to provide one functionality. Like select and option tag in html are used to create a DropDown. React context is used extensively here.
// App.tsx
import { useState } from "react";
import "./styles.css";
import Tab from "./Tab";
export default function App() {
const [currentIndex, setIndex] = useState(0);
const handleChange = (newIndex) => {
setIndex(newIndex);
};
return (
<div className="App">
<Tab value={currentIndex} onChange={handleChange}>
<Tab.Heads>
<Tab.Item label={"Tab1"} index={0} />
<Tab.Item label={"Tab2"} index={1} />
<Tab.Item label={"Tab3"} index={2} />
</Tab.Heads>
<Tab.ContentWrapper>
<Tab.Content index={0}>
<h1>I am content 1</h1>
</Tab.Content>
<Tab.Content index={1}>
<h1>I am content 2</h1>
</Tab.Content>
<Tab.Content index={2}>
<h1>I am content 3</h1>
</Tab.Content>
</Tab.ContentWrapper>
</Tab>
</div>
);
}
// Tab.tsx
import { createContext, useContext } from "react";
import "./Tab.css";
const TabContext = createContext();
export default function Tab({ children, value, onChange }) {
return (
<div>
<TabContext.Provider value={{ value, onChange }}>
{children}
</TabContext.Provider>
</div>
);
}
Tab.Heads = ({ children }) => {
return <div className="heads">{children}</div>;
};
Tab.Item = ({ label, index, children }) => {
const { value, onChange } = useContext(TabContext);
const handleClick = () => {
onChange(index);
};
return (
<div
onClick={handleClick}
className={`item ${index === value ? "active" : null}`}
>
{label}
</div>
);
};
Tab.ContentWrapper = ({ children }) => {
return <div className="contentWraper">{children}</div>;
};
Tab.Content = ({ children, index }) => {
const { value } = useContext(TabContext);
return value === index ? <div>{children}</div> : null;
};
Hooks Pattern
Although Hooks are not necessarily a design pattern, Hooks play a very important role in your application design. Many traditional design patterns can be replaced by Hooks.
Rules:
Calls hooks on top level.
Call hooks only in React functional components
Example:
// ChatPage.tsx
import useOnline from "./useOnline";
export default function ChatPage() {
const isOnline = useOnline();
return isOnline ? "User available for chat" : "User not available for chat";
}
// useOnline.tsx
import { useEffect, useState } from "react";
export default function useOnline() {
const [isOnline, setOnline] = useState(false);
useEffect(() => {
setTimeout(() => {
setOnline(true);
}, 3000);
}, []);
return isOnline;
}