Prop Drilling in React
While developing react applications, sometimes it might be challenging to work with state management. There are various State management solutions available(prop drilling, Context API, external state management libraries like Redux, etc.), and they all come with their pros and cons.
What is State Management?
One of the crucial and inescapable aspects of every dynamic application is state management. State management in a React component is supported via React's straightforward and adaptable API.
The state of a React component at a certain instance represents the value of one of its dynamic attributes, like what checkboxes are checked, what text is entered into the text fields, etc. For each component, a dynamic data store is provided by React. Using the component's this.state member variable(in case of Class Components) or useState() hook(in case of Functional components), one can access the internal data that represents the state of a React component. When a component's state changes, it will automatically re-render itself by invoking the render() method with the updated state.
Analyzing a real-time clock component might serve as an easy example to help one grasp state management. The primary function of the clock component is to display the current date and time at a specific location. The clock component should keep the current date and time in its state because the current time will change every second. The render() function of the clock is invoked each second because the state of the clock component changes each second, and the render() method uses the clock's current state to display the current time.
What are Props?
When React recognizes an element as a user-defined component, it sends the component’s JSX properties and children as a single object. This item is known as props.
For instance, the following code displays Hello, Hulk on the page :
Introduction to Prop Drilling
Data is frequently transferred amongst components in a standard React app using props. Sharing this information manually might be difficult, especially when it involves several nested components. Additionally, it may be difficult to share data between two child components. As a result, global state management is required.
Prop drilling in react is the process of passing data from one component via several interconnected components to the component that needs it.
The name drilling refers to this process of forcing these components to take in unnecessary data and pass it on to the following component, which in turn sends it on to the next component, and so on until it reaches its intended destination. The reusability of components and app performance may suffer as a result in a significant way.
It is not a good idea to pass data via several components when building neat, reusable, and DRY code.
Prop drilling in react is sometimes advantageous for smaller apps since there are lesser components and conditions to control.
Why We shouldn’t Use Prop Drilling?
You must manually send the state and data through all of the intermediate levels that do not need it to update the state of the component lower down in the tree. The result is long and challenging to maintain code.
Furthermore, there are greater possibilities for mistakes like renaming the props midway by mistake, refactoring some data's structure, props being forwarded more often than is necessary, using default props unfairly, or using default props unfairly or insufficiently.
Additionally, there are numerous other circumstances in which prop drilling in react can be quite frustrating during the maintenance and refactoring process, this could become even more complicated in large-scale projects.
Creating React Application
Use the following command to create the React Application.
Then run the following command to move to the ReactApp folder.
Example - 1 : With Using Prop Drilling
In this code, the title prop is passed from the Parent component and then to the ChildA, then to ChildB, then finally to the ChildC component.
Example - 2 : Without Prop Drilling
The issue with prop drilling in react is that data from the parent component must always arrive from every level, even if it is only required at the very end and not at any other levels.
Using the useContext hook is one of the better solutions to this. The useContext hook uses the Provider and Consumer mechanisms and is based on the Context API. This approach will be covered in more detail in the article.
In the code mentioned above, it can be observed that the prop is not drilled down to the ChildC component. Instead, it is called directly in the ChildC component.
Developing an Application of Prop Drilling with Several Levels of Nesting
Let us consider an app where the user is welcomed by their name upon logging in to the application. The structure and the hierarchy of the app are mentioned in the following images :
We are having trouble because the component generating the message of welcome is deeply buried inside our application, while the user object holding the name of the user is only accessible at the level where the root component is available. This implies that we must somehow transmit this user object to the part that generates the message of welcome.
The user object prop is shown by the blue arrows as it descends from the root component that is App, via numerous intermediate components, onto the specific Message component that requires it. Finally, it displays the welcoming message along with the name of the user who has signed in.
This is an example of a prop drilling situation. To get around this seeming issue, developers frequently turn to the Context API without carefully considering the various issues that could result.
To avoid any external imports and for the sake of simplicity, we have declared different components in a single file rather than splitting them.
Fixing the Prop Drilling by Using the Context API
Let us understand a bit about the Context API before solving prop drilling in react.
By enclosing your state and data in a context provider, the Context API essentially allows you to transmit your state and data to numerous components. Afterward, the state is sent to the context provider by utilizing its value attribute. To connect to the context provider, the children components make use of a context consumer or they can use the hook useContext(), and then the children components can use the state which is received from the context provider.
To establish a context, we first import a createContext Hook. Next, we import a useContext Hook that will retrieve the state supplied by a context provider.
The createContext Hook method is then used, and it returns a context object with no data. This is then saved in a userContext variable. Now, the MainPage component is wrapped with the Context. Provider and the user object are then passed to it, which will then provide it to all components that are nested inside it.
Finally, we use the useContext Hook and a little destructuring to retrieve this user from the Message component, which is nested inside the MainPage component. The requirement to transmit the user prop via the intermediary components has been fully eliminated. Now we have solved the prop drilling in react by using the Context API.
The Two Major Drawbacks of Relying Heavily on the Context API
By incorporating the Context API into our program, we were able to solve the prop drilling problem, but it is not without its drawbacks, such as issues with the reusability of components and performance.
These drawbacks do not have many consequences in small applications, though they might produce unwanted results.
The Context docs also mention these drawbacks.
Before Using Context following things should be kept in mind.
When a large number of components at various nesting depths require access to a single piece of data, context is usually used. It makes component reuse very challenging, so use it carefully.
The component composition is frequently a more straightforward approach than the context API if you merely want to avoid giving particular props through numerous layers.
Let us now understand these drawbacks in detail.
Issues in reusability of component :
We implicitly transfer any state or data that is saved in a context provider to the child components it encapsulates when that provider is wrapped over multiple components.
Even though we do not give the state to these components until we start a context consumer or use a context hook, we have nonetheless made them implicitly reliant on the state provided by the context provider.
Reusing any of these parts outside the scope of the context provider is where the issue lies. Before rendering, the component initially tries to verify that the implicit state provided by the context provider is still present. A render error is thrown when it can not find this state.
Let us consider the prior scenario for a moment. Consider the scenario where the Message component was to be declared outside the context provider wrapper and was to be reused to show a different message based on some other condition.
We will receive the TypeError: Cannot destructure property 'user' of 'Object(...)(...)' as it is undefined.
The Message component is now reliant on the user object in the context provider's state, as can be seen above, so any attempt to accomplish this will likewise result in a render error. Any existing user object provided by the context provider cannot be accessed through it.
For small apps, this issue can be solved by using Context API, but that is not the case with large applications.
Issues with performance :
Every time a change takes place, the Context API transmits this change to every component utilizing its provider, which causes these components to be re-rendered. The Context API employs a comparison algorithm to compare the value of its current state to any update it receives.
At first sight, this might appear insignificant, but when we excessively rely on Context for fundamental state management, we over-engineer our app by unnecessarily forcing all of our states into a context provider. This is not very efficient when several components rely on this Context Provider because they will re-render every time the state changes, whether or not the change affects or concerns them.
When creating React apps, we do it by creating a variety of reusable components, which may be thought of as being similar to independent bricks. Then, each brick (component) is viewed as a separate part of our final interface, which, when put together or constructed, constitutes the entire interface of our program.
Component composition is the process of putting together components like bricks.
You may have used component composition when designing React applications in the past without realizing what it was : a different method for controlling the state of your app. In this article, we will primarily concentrate on two kinds of component composition : container components and specialized components.
Container Components :
Method - 1 :
It is possible to explicitly give one or more components to another component as its prop, allowing those components to be retrieved and rendered inside of that component.
To manually transfer the desired child components to the parent component with the required data attached directly to the child component, we may just take these components to our root component rather than layering components within components and then struggling to pass data to them via prop drilling. It will then be rendered as a prop by the parent component.
Method - 2 :
By encircling one or more children components with a parent component and grabbing those children components with the default children prop
Each time a component is wrapped around the other component, the wrapping component elevates the position of a parent component to the component that is wrapped. The default children prop, which is in charge of generating child components, can then be used to receive the child component inside the parent component.
Recreating Our App Using Component Composition
Now we will rewrite our application so that it uses component composition. We will execute it twice to highlight its adaptability.
Component composition can be done in a variety of ways, as demonstrated in the two samples above. In the first excerpt, we used React's props functionality to feed the component into each parent as a basic object with the relevant data attached.
The data was provided directly to the component of interest in the second sample, which was a pure composite of our layout thanks to the children attribute. We could easily think of other methods to modify this app using only component composition. Still, by this point, you should be able to understand how to prop drilling in react could be resolved using only component composition.
Elevate Your React Journey! Enroll in Scaler's Full Stack Developer Course, Guided by Industry Gurus. Certify Your React Expertise Today!
We learned the following points in this tutorial :
- The state of a React component at a certain instance represents the value of one of its dynamic attributes, like what checkboxes are checked, what text is entered into the text fields, etc.
- When React recognizes an element as a user-defined component, it sends the component’s JSX properties and children as a single object. This item is known as a prop.
- Prop drilling is the process of passing data from one component via several interconnected components to the component that needs it.
- Prop drilling results in long and unclean code, and also there are greater possibilities for mistakes like renaming the props midway by mistake, refactoring some data's structure, props being forwarded more often than is necessary, using default props unfairly or using default props unfairly or insufficiently.
- By enclosing your state and data in a context provider, the Context API essentially allows you to transmit your state and data to numerous components. Afterward, it uses its value attribute to send this state to the context provider.
- Component composition is the process of putting together components like bricks to create a final product.
- There are two kinds of component composition :
- container components and
- specialized components