DEV Community

loading...
Cover image for Composition vs Context in React

Composition vs Context in React

bhupendra
I am front end developer. I love exploring JS through IOT (Nodebots) , WebVR. Passionate about Web Performance , React & Graph QL
・2 min read

In a React App , there are two types of states that needs to be managed :

  1. Sever Cache : state that is unique for a page and syncs to DB
  2. UI State : state that refreshes on reload of page

When we talk about State Management in React , we refer to the UI State . Based upon how many components share the same state either we can Lift the State up ⬆ to the least common parent element Or we can co-locate⬇ the state to the component where it's used. This is explained in depth by Kent in his blog.

The problem arises when we have the state at top and it has to be passed via props to a deeply nested component, although the intermediate components don't require the state value , but they have to receive and forward to any of child component. This behavior is called Props drilling .

function App() {
  const [status, setStatus] = React.useState(false);
  return <Header status={status} />;
}

const Header = ({ status }) => {
  return <Account status={status} />;
};

const Account = ({ status }) => {
  return status ? "LogOut" : "LogIn";
};
Enter fullscreen mode Exit fullscreen mode

In above code Header Component does not require status prop but only forwards it to the Account component which shows Log Out/In option based upon status prop.
image

First thing that comes to mind in solving Props Drilling problem is to use the Context API.

Let's see how to avoid props forwarding with context API :

const StatusContext = React.createContext();

function App() {
  const [status, setStatus] = React.useState(false);
  return (
    <StatusContext.Provider value={status}>
      <Header />
    </StatusContext.Provider>
  );
}

const Header = () => {
  return <Account />;
};

const Account = () => {
  const status = React.useContext(StatusContext);
  return status ? "LogOut" : "LogIn";
};
Enter fullscreen mode Exit fullscreen mode

If you only want to avoid passing some props through many levels, component composition is often a simpler solution than context. ~ React docs

, but before we try to solve props drilling with Composition,let's understand what does composition mean ~ Composition means building ⚒ or choosing what goes inside a component. So instead of writing

<Header />

, we can refer it to as

<Header> <Account status={status} /></Header>

React by default provides a children prop to all nested components and it's using this special props it becomes possible to make scalable & reusable components.

Let's see how to apply solve props drilling problem by composition :

function App() {
  const [status, setStatus] = React.useState(false);
  return (
    <Header>
      <Account status={status} />
    </Header>
  );
}

const Header = ({ children }) => {
  return <>{children}</>;
};

const Account = ({ status }) => {
  return status ? "LogOut" : "LogIn";
};
Enter fullscreen mode Exit fullscreen mode

💡Context should be used when data is needed for many components at different nesting levels. Using Context makes the component less reusable as the consumer component can't be used outside the provider component hierarchy.

Complete Example with both approaches :

Discussion (1)

Collapse
lukeshiru profile image
LUKESHIRU

Account could be just a regular function, because is just returning a string:

const account = ({ status }) => (status ? "LogOut" : "LogIn");
Enter fullscreen mode Exit fullscreen mode

I don't think this is the best example for Composition vs. Context, but I see what you want to show. Basically is better to use children than context for scenarios like this, and I agree. Maybe making Account an actual component will make it more obvious. Maybe something like this:

import { useState } from "react";

const App = () => {
    const [connected, setConnected] = useState(false);

    return (
        <Header>
            <ConnectionButton connected={connected} />
        </Header>
    );
};

/** @type import("react").FC<JSX.IntrinsicElements["header"]> */
const Header = props => <header {...props} />;

/** @type import("react").FC<JSX.IntrinsicElements["button"] & { connected?: boolean }> */
const ConnectionButton = ({ children, connected, ...props }) => (
    <button {...props}>{children ?? (connected ? "Log Out" : "Log In")}</button>
);
Enter fullscreen mode Exit fullscreen mode

Cheers!