Coupling refers to the degree of interdependence between different modules or classes in a system.
What is Coupling?
Coupling speaks to how closely connected different components or modules are within an application. High coupling means that changes in one module may necessitate changes in many other modules. This results in a codebase that becomes increasingly difficult to maintain and modify over time. As such, best practices usually recommend minimizing coupling.
Separate Components by Responsibility
A prevalent form of coupling occurs when a single file or component assumes multiple responsibilities (Look at the Single Responsibility Principle). We should avoid patterns where a component takes on multiple roles; For example, having a component determine if it should render as a header or footer based on props:
const HeaderOrFooter = ({ isHeader }) => {
if(isHeader){
return (<div className="header">...</div>)
}
return (<div className="footer">...</div>)
}
Instead, consider creating distinct components for different roles and handling display logic at the parent level:
const ParentComponent = () => {
...
isHeader ? <Header /> : <Footer />
}
const Header = () => {
return (<div className="header">...</div>)
}
const Footer = () => {
return (<div className="footer">...</div>)
}
Avoid Prop Drilling
Prop drilling refers to the practice of passing props from a parent component down through its descendants. If your components are tightly intertwined with a shared state, it's a clear sign of tight coupling.
const ParentComponent = () => {
...
const [prop1, setProp1] = useState()
const [prop2, setProp2] = useState()
const [prop3, setProp3] = useState()
<ChildComponent prop1={prop1} .... prop3={prop3}/>
}
const ChildComponent = ({prop1, prop2, prop3}: IChildComponent) => {
<GrandChildComponent prop1={prop1} .... prop3={prop3}/>
}
To combat this, you can use shared context or architect your components with a clearer separation of concerns. Remember to use context carefully to avoid bloating the global state.
const ParentComponent = () => {
const [prop1, setProp1] = useState()
const [prop2, setProp2] = useState()
const [prop3, setProp3] = useState()
...
<ChildComponent />
}
const ChildComponent = () => {
....
<GrandChildComponent />
}
const GrandChildComponent = () => {
const {prop1, prop2, prop3} = useContext('SharedContext');
return (...)
}
Fetch Data Close to Where It's Used
This is more of a personal preference, but I like fetching and managing data as close to its usage point as possible. This helps reduce unnecessary props passed down and keeps data handling within the concerned component.
const ParentComponent = () => {
...
const data = useGetData()
<ChildComponent data={data}/>
}
A better approach:
const ParentComponent = () => {
...
<ChildComponent />
}
const ChildComponent = () => {
data = useGetData();
....
}
In conclusion, managing coupling is essential for scalable and maintainable applications. By separating concerns, avoiding prop drilling, and fetching data where it's used, we can significantly reduce the effects of high coupling and build more robust React applications.
Top comments (0)