Ever since React 16.8 introduced us to React hooks, many developers started leveraging React context as a trim solution to the tedious task of "prop drilling".
What is "Prop Drilling"?
React's design revolves around building UI components, which, when bundled together, form a component tree hierarchy. For a deep nested component to access data from higher up, the data must be cascaded downwards as props, the building blocks of React components. Efforts to evade prop drilling are mainly aimed at reducing redundant component re-renders. When a higher-level component modifies its data and disseminates it using props, all intervening components get triggered to re-render.
(image sourced from the React documentation)
How does React Context help?
React Context offers an excellent solution to prop drilling. It enables smooth transportation of data directly to the components that need it, bypassing intermediary children components.
Essentially, Context represents React's interpretation of dependency injection.
Sample Code Illustration:
1const ThemeContext = React.createContext();
2
3function ThemeProvider(props) {
4 const [theme, setTheme] = React.useState("dark");
5 const value = [theme, setTheme];
6 return <ThemeContext.Provider value={value} {...props} />;
7}
8
9function ThemeDisplay() {
10 const [theme] = React.useContext(ThemeContext);
11 return <div>{`The current theme is ${theme}`}</div>;
12}
13
14function Theme() {
15 const [, setTheme] = React.useContext(ThemeContext);
16 const changeTheme = () => setTheme("light");
17 return <button onClick={changeTheme}>Change theme to light</button>;
18}
19
20function App() {
21 return (
22 <div>
23 <ThemeProvider>
24 <ThemeDisplay />
25 <Theme />
26 </ThemeProvider>
27 </div>
28 );
29}
30
The Contention Surrounding Context Usage
Although the use of React Context is becoming more common, even the official React documentation advises against its overuse.
// ... rest of the code remains unchanged
Embracing React Composition
An alternative strategy worth considering is using JSX as children, leveraging the power of composition to achieve results similar to the Context API.
While this strategy doesn't directly tackle the issue of re-rendering all child components, it offers significant benefits in terms of component reuse. For example, the Dashboard Content component, which doesn't require user data, can be reused elsewhere for a different Dashboard without needing a context provider, thereby avoiding implicit dependencies.
Sample Implementation:
1export default function App() {
2 const [user, setUser] = React.useState(null);
3
4 return (
5 <div>
6 {user ? (
7 <LoggedinWrapper>
8 <Homepage>
9 <LogoutButton onLogout={() => setUser(null)} />
10 <Navigation />
11 <HomepageContent>
12 <Profile user={user} />
13 </HomepageContent>
14 </Homepage>
15 </LoggedinWrapper>
16 ) : (
17 <SigninPage onLogin={() => setUser({ name: 'Bob' })} />
18 )}
19 </div>
20 );
21}
22
23function LogoutButton({ onLogout }) {
24 return <button onClick={onLogout}>Logout</button>;
25}
26
27function LoggedinWrapper({ children }) {
28 return (
29 <React.Fragment>
30 <h1>You are signed in!</h1>
31 {children}
32 </React.Fragment>
33 );
34}
35
36function Navigation() {
37 return (
38 <React.Fragment>
39 <h2>Dashboard Nav</h2>
40 </React.Fragment>
41 );
42}
43
44function Homepage({ children }) {
45 return (
46 <React.Fragment>
47 <h3>Dashboard</h3>
48 {children}
49 </React.Fragment>
50 );
51}
52
53function HomepageContent({ children }) {
54 return (
55 <React.Fragment>
56 <h3>Homepage</h3>
57 {children}
58 </React.Fragment>
59 );
60}
61
62function Profile({ user }) {
63 return (
64 <ul>
65 <li>Username: {user.name} </li>
66 </ul>
67 );
68}
69
70function SigninPage({ onLogin }) {
71 return (
72 <React.Fragment>
73 <h1>Sign In</h1>
74 <button onClick={onLogin}>Login</button>
75 </React.Fragment>
76 );
77}
78
In contrast, the mandatory need for a context provider can be challenging when trying to render a component outside its allotted provider. Often, developers encapsulate the entire application within a global context provider, which could lead to performance issues, as previously mentioned.
In a nutshell, composition can serve as a feasible alternative to relying on React context to prevent prop drilling.