Plumbing in programming is something you have to do to make things work. It's sometimes repetitive or looks like a boilerplate you would usually remove. It's also crucial because it connects different layers of the application. In this post, I'll show you a typical example of plumbing inside a React application and a way to fix it.
Let's take a look at a pretty standard, table-sidebar layout:
<Table
selectedProduct={product}
products={products}
onProductChange={handleProductChange}
/>
<Sidebar product={product} />
You can find the complete project here, without using hooks (this is not the recommended approach).
The table contains a list of products. When one of them, is clicked, the sidebar shows the details of the product and highlights the row clicked. Both Table
and Sidebar
need to know about the selected product
.
Table
needs to highlight the row:
const Table = ({ selectedProduct, products, onProductChange }) => (
<tbody>
{products.map(currProduct => (
<TableRow
selected={isSelected(currProduct, selectedProduct)}
key={currProduct.id}
product={currProduct}
onProductChange={onProductChange}
/>
))}
</tbody>
);
export default Table;
and Sidebar
displays additional information about the selected product:
const Sidebar = ({ product }) => (
<div className="sidebar">
<h3>{product.name}</h3>
<p>ID: {product.id}</p>
<p>SKU: {product.sku}</p>
</div>
);
export default Sidebar;
Later we decide to extend the sidebar with some sections:
const Sidebar = () => (
<div className="sidebar">
<DetailsPanel />
<DimensionsPanel />
</div>
);
As the application grows, we create wrapper components like Sidebar
. These components don't immediately use product
, only pass it down to child components.
React.useContext
useContext
is one of the three basic React hooks.
The great thing about Contexts is that the component subscribed to it automatically re-renders when the Context value changes. Our Table/Sidebar layout sounds like an ideal use case for this.
Creating a context is as simple as:
const TableContext = React.createContext();
export default TableContext;
and to subscribe to it:
const value = React.useContext(TableContext);
Use <TableContext.Provider>
to set the value
of the context. We wrap both Table
and Sidebar
components with this component because we want both of them to update when the context value changes:
<TableContext.Provider value={product}>
<Table
products={products}
onProductChange={handleProductChange}
/>
<Sidebar />
</TableContext.Provider>
Now instead of passing the selected product to the Sidebar component, we set it as the current value of <TableContext.Provider>
. Finally, we subscribe Sidebar
to context updates:
const Sidebar = () => {
const product = React.useContext(TableContext);
return (
<>
<h3>{product.name}</h3>
<p>ID: {product.id}</p>
<p>SKU: {product.sku}</p>
</>
);
}
export default Sidebar;
The use of context allowed us to remove the product prop from the Sidebar
component. We see the real benefit of useContext
as our application grows. Earlier we presented how the Sidebar
would look like after some development and how different Sidebar panels would need a reference to the current product. With the use of React.useContext passing down this selection through multiple layers of components is not required anymore. We subscribe to product updates with each panel as we did above with the Sidebar
and this simplifies the component:
const Sidebar = () => (
<div className="sidebar">
<DetailsPanel />
<DimensionsPanel />
</div>
);
See this code in action here.
Another benefit of not passing down the same object to multiple components is avoiding repeated PropTypes declarations. Googling for "react PropTypes repeated" this looks like a problem React developers requently encounter. With the above approach, there is one place left to define the shape of the product:
const TableContext = React.createContext();
TableContext.Provider.propTypes = {
value: PropTypes.shape({
id: PropTypes.string,
name: PropTypes.string,
sku: PropTypes.string,
}),
}
export default TableContext;
Did you find React Context useful in some other scenarios? Let me hear your story in the comment section below!
If you enjoyed this content, please show the love by sharing it with others. Thank you!
Cover photo made by Samuel Sianipar on Unsplash.
Top comments (0)