DEV Community

Cesar Vega
Cesar Vega

Posted on

Understanding Coupling in React: Best practices and Examples

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>)   
}
Enter fullscreen mode Exit fullscreen mode

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>)   
}
Enter fullscreen mode Exit fullscreen mode

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}/>
}
Enter fullscreen mode Exit fullscreen mode

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 (...)
}
Enter fullscreen mode Exit fullscreen mode

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}/>
}
Enter fullscreen mode Exit fullscreen mode

A better approach:

const ParentComponent = () => {
    ...
    <ChildComponent />
}

const ChildComponent = () => {
    data = useGetData();
    ....
}
Enter fullscreen mode Exit fullscreen mode

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)