Component Composition in React
Though it seems the nascent steps in the learning phase of ReactJS, there is a major difference in outcomes if we take different approaches in defining and embedding the components inside another one. To explore this further, let’s conduct an experiment using an example.
1. From the below code, we have two components App
(parent) and Title
(child), pretty much simple. (a) An input field and (b) an Incrementor that only updates at the click of a button.
import "./styles.css";
import { useState } from "react";
const Title = () => {
const [title, setTitle] = useState("");
return (
<input
type="text"
placeholder="Enter your title"
value={title}
onChange={(e) => setTitle(e.target.value)}
/>
);
};
export default function App() {
const [increment, setIncrement] = useState(0);
return (
<div className="App">
<h2>Embedded Component</h2>
<Title />
<div className="t_margin">
<button onClick={() => setIncrement((prev) => prev + 1)}>
Incrementer {increment}
</button>
</div>
</div>
);
}
2. Both features work well and independently.
3. Now, let’s define the Title
component inside the App
component and observe how its behavior is affected.
4. We observed that when we update the state of the App
component by clicking Incrementer
, the Title field reset. Basically, Title
field is unmounted and remounted.
5. In React, when a component’s state or props change, it triggers a re-render of the component and its children. This involves calling the render function again to update the UI. However, when a component is unmounted and then remounted, its internal state is reset to its initial values.
6. We can verify the above point by using useEffect
like how Title
component gets mounted and unmounted.
7. useEffect
hook inside the Title
component is configured to run only once, during the initial mounting of the component. Since the Title
component is unmounted and mounted again when the App
component re-renders, the useEffect
hook is triggered twice, resulting in the "Title mounted" and "Title unmounted" log statements appearing twice in the console.
8. That is why, in order to avoid these types of needless re-rendering, we should always be defined outside of other components. This ensures that the component remains stable and does not get redefined during each re-render.
9. Apart from that, there are multiple other reasons to go with the suggested approach like Component Reusability, Code Organization, and Readability.
Still curious about double re-rendering 🤔?
The double re-rendering occurs because of the specific structure of the code and the behavior of the
useState
hook used in theApp
component.When we call the
setIncrement
function to update theincrement
state in theApp
component, React first schedules a re-render of the component. During this re-rendering process, the updated state is applied, and the component is rendered again.However, React performs a shallow comparison of the previous and updated states. If the state values are the same after the initial re-render, React optimizes the process by not re-rendering the component again. This optimization is known as “shouldComponentUpdate” in class components and “React.memo” in function components.
In the code, when the
increment
state is updated, it triggers a re-render of theApp
component. During this first re-render, React applies the updated state value and renders the component again.Now, after the first re-render, React performs the state comparison and determines that the state values have not changed. As a result, React optimizes and skips the second re-render. This behavior reduces unnecessary re-rendering and improves performance.
Therefore, even though you update the state of the
App
component once, it gets re-rendered twice due to the initial re-render triggered by the state update and the subsequent optimization performed by React.