Whenever people start learning React, they inevitably encounter the challenge of handling props. Props are essential for passing data from parent to child components, enabling the creation of dynamic and reusable UI elements. However, as applications grow in complexity, developers often face a common hurdle known as prop drilling. This occurs when props must traverse multiple intermediary components to reach deeply nested children. In this article, we will explore the pitfalls of prop drilling and discuss how modern state management solutions, such as Recoil, can simplify and enhance your React development experience.
Let’s begin with a product list prop drilling example:
Components Breakdown:
App Component:
function Home() { const [data, setData] = useState<Array<Product>>([]); const [loading, setLoading] = useState<boolean>(true); async function loadData() { const res = await fetch("https://fakestoreapi.com/products"); let json = await res.json(); json = json.slice(0, 3); setData(json); setLoading(false); } useEffect(() => { loadData(); }, []); return ( <main className="min-h-screen flex items-center justify-center"> {loading ? <div>Loading...</div> : <ShoppingList products={data}/>} </main>);
}
Shopping list Component:
function ShoppingList({products}: {products: Array<Product>})
{ return ( <div className="flex gap-3 items-stretch"> {products && products.map(product=> <ProductCard product={product}/>)}
</div>
)
}
Product Component:
function ProductCard({product}: {product: Product}){ return ( <div> <div className="bg-white border border-yellow-700 rounded-lg shadow dark:bg-gray-800 dark:border-gray-700 w-72 h-96"> <img className="py-8 rounded-lg h-52 pl-20" src={product.image} alt="product image" /> <div className="px-5"> <h5 className="text-xl font-semibold tracking-tight text-gray-900 dark:text-white">{product.title}</h5>
<div className="flex items-center mt-2.5 mb-5"> <Star rating={product.rating.rate}/>
<span className="bg-blue-100 text-blue-800 text-xs font-semibold px-2.5 py-0.5 rounded dark:bg-blue-200 dark:text-blue-800 ms-3"> {product.rating.rate} </span>
</div>
<div className="flex items-center justify-between"> <span className="text-3xl font-bold text-gray-900 dark:text-white">${product.price}</span>
<div className="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800">Add to cart</div>
</div>
</div>
</div>
</div>)
}
Star Component
function Star({rating}: {rating: number}){ let noRating = 5 - Math.floor(rating); return ( <> <div className="flex items-center space-x-1 rtl:space-x-reverse"> {Array.from({ length: rating }).map((_, index) => (<svg className="w-4 h-4 text-yellow-300" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 22 20"> <path d="M20.924 7.625a1.523 1.523 0 0 0-1.238-1.044l-5.051-.734-2.259-4.577a1.534 1.534 0 0 0-2.752 0L7.365 5.847l-5.051.734A1.535 1.535 0 0 0 1.463 9.2l3.656 3.563-.863 5.031a1.532 1.532 0 0 0 2.226 1.616L11 17.033l4.518 2.375a1.534 1.534 0 0 0 2.226-1.617l-.863-5.03L20.537 9.2a1.523 1.523 0 0 0 .387-1.575Z"/> </svg>))}
{Array.from({ length: noRating }).map((_, index) => (<svg className="w-4 h-4 text-gray-200 dark:text-gray-600" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 22 20"> <path d="M20.924 7.625a1.523 1.523 0 0 0-1.238-1.044l-5.051-.734-2.259-4.577a1.534 1.534 0 0 0-2.752 0L7.365 5.847l-5.051.734A1.535 1.535 0 0 0 1.463 9.2l3.656 3.563-.863 5.031a1.532 1.532 0 0 0 2.226 1.616L11 17.033l4.518 2.375a1.534 1.534 0 0 0 2.226-1.617l-.863-5.03L20.537 9.2a1.523 1.523 0 0 0 .387-1.575Z"/> </svg>))}
</div>
</>
)
}
This leads to unnecessary re-renders of components involving props and impact performance. Sometimes passing props to component without context of usage leads to debugging more difficult, as it is harder to trace the flow of data through the component tree.
To address the issues associated with prop drilling, several alternatives can be used and React Context API allows you to create a context and share data across the component tree without passing props explicitly at every level.
Setting up for ContextAPI:
1. Creating context
import { createContext } from "react";
import { Product } from "../types/types.next"; export const ProductContext = createContext<Product[] | null>(null);
2. Wrap the Context Provider around Parent div
export default function Home() { const [data, setData] = useState<null | Product[]>(null); const [loading, setLoading] = useState<boolean>(true); async function loadData() { const res = await fetch("https://fakestoreapi.com/products"); let json = await res.json(); json = json.slice(0, 3); setData(json); setLoading(false); } useEffect(() => { loadData(); }, []); return ( <main className="min-h-screen flex items-center justify-center"> {loading ? <div>Loading...</div> : <ProductContext.Provider value={data}> <ShoppingList/> </ProductContext.Provider>}
</main>);
}
3.Use useContext hook to use data
function ShoppingList()
{ const products = useContext(ProductContext); return ( <div className="flex gap-3 items-stretch"> {products && products.map(product=> <ProductCard product={product}/>)}
</div>
)
}
That’s how we got global point of management for our props facilitating simplified component communication. But this comes with costs. Context providers trigger a re-render in all consuming components whenever the context value changes, regardless of whether the component actually uses that part of the context. This can lead to unnecessary re-renders and impact performance, especially if the context value changes frequently.
It leads to path for consideration of dedicated state management library like Redux or Recoil might better suit your application’s needs, especially for complex global state management scenarios.
Discover more from Coursity
Subscribe to get the latest posts sent to your email.