<Article/>
Managing Data in React Components: Scope, Immutability, and Performance
optimization · Sep 4, 2023 · 6
- <optimization/>
- <components/>
- <react/>
Recently, I was working on a react project and fell into a dilemma: Where is the right place to declare my data? ‘Outside the component’ or ‘within the component’. Here’s what I mean, Check code sample 1 vs sample 2 below.
Sample 1:
// Sample 1
import React from "react";
// Data
const books = ["book1", "book2", "book3"];
const Books = () => {
return (
<div>
{books.map((book, index) => (
<p key={index}>{book}</p>
))}
</div>
);
};
export default Books;
Sample 2:
// Sample 2
import React from "react";
const Books = () => {
// Data
const books = ["book1", "book2", "book3"];
return (
<div>
{books.map((book, index) => (
<p key={index}>{book}</p>
))}
</div>
);
};
export default Books;
In my early React days, I was firmly in the “declare inside the component” camp, much like what we see in Sample 2. It felt natural, and it worked. However, as my React journey continued and I delved deeper into how React components work under the hood I began to have some doubts.
Here’s a not too long explanation: When you create a component with data or functions declared before the inbuilt react ‘render’ method or in this case the ‘return’ statement, and that component gets reused or re-rendered, every time it happens, all that data is reconstructed from scratch. Imagine baking a fresh batch of cookies every time you want a snack. Yummy, but not very efficient. You can check it out with this sample,
import React, { useState } from "react";
const testFunction1 = (() => {
console.log("I will log only once!");
})();
const Counter = () => {
const [count, setCount] = useState(0);
//IIFE - Immediately invoked function expression
const testFunction2 = (() => {
console.log("I logged because this component just rendered or re-rendered");
})();
const incrementCount = () => setCount((prev) => prev + 1);
const decrementCount = () => setCount((prev) => prev - 1);
return (
<div>
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", fontSize: "2em", gap: "1em"}}>
<button onClick={decrementCount} style={{ padding: "0.8em 1em", border: "1px solid" }}> - </button>
<h1>Count: {count}</h1>
<button onClick={incrementCount} style={{ padding: "0.8em 1em", border: "1px solid" }}> + </button>
</div>
</div>
);
};
export default Counter;Open up your console and you’d notice as you increment or decrement the counter with the state updates the count value and hence the component re-renders, you’d notice the log statements, the ‘testfunction1’ is only created once but the ‘testFunction2’ is recreated as many times as the count changes and the component re-renders. The ‘incrementCount’ and ‘decrementCount’ functions are recreated too but we don’t exactly see that, but yeah, you get the point, yes???
I wondered: could this be impacting my app’s performance? This question prompted a shift in my approach. I started to declare any data that I needed to remain immutable outside the component, similar to what we have in ‘Sample 1’.
But then, I stumbled upon various React codebases, each flaunting its own style. This got me thinking — what is the “right” way, and why?
The Best on Twitter: "React devs on TL. Which would you choose? A or B.Would really appreciate if you can give your reason.PS: The diff is just where the 'BookData' is declared.#reactjs pic.twitter.com/UZH2jg6jS4 / Twitter"
React devs on TL. Which would you choose? A or B.Would really appreciate if you can give your reason.PS: The diff is just where the 'BookData' is declared.#reactjs pic.twitter.com/UZH2jg6jS4
I even asked the community on twitter and it turned out most people preferred ‘Sample 1’ over ‘Sample 2’. Their reasons included readability, clean code, avoiding the re-rending issue I talked about.
While I gained some insights, I wasn’t exactly satisfied with the answers I got and I sort to look for more answers and here are my findings:
First, in comparing Sample 1 to Sample 2, I found that while Sample 1 would work, it could lead to potential bugs. For example, if you have multiple instances of the Books component, they would all share the same books array. If any component modifies the books array, it will affect all instances, potentially leading to unexpected behaviour and bugs. See case below.
books[0] = "newBook";
Then Sample 2 (declaring data within a component) is generally thought to be safer and more predictable in React applications because each component instance maintains its own state and data. Modifying the books array within one instance of the component won't affect other instances.
Regarding performance, recreating the books array or any other data on each render can have some implications, but in most cases, this won't lead to significant performance problems as modern JavaScript engines are optimised to handle such scenarios efficiently. However, if the books array were much larger and the component were being re-rendered frequently (such as in a complex animation or with a large number of components), it might be worth considering optimisations like using the React.memo Higher Order Component to prevent unnecessary re-renders or moving the books array outside of the component if it doesn't need to be recreated on every render.
So to wrap this up, this is my suggestion:
1. If you favour clean, readable and DRY code, you can declare your data outside the component or even create a file for it and export it for use amongst shared components. Just be certain that none of the components using that data would ever attempt to alter that data or its content.
2. You can still declare your data within your component and only have to worry about performance issues when that data is a huge data. Even ‘Chat GPT’ advised:
Premature optimization should be avoided, and it’s generally best to write code that is clear and maintainable first. If you notice actual performance issues, you can then profile and optimise accordingly.
That’s about it. Please do share your take on the matter? Do you lean towards declaring data inside the component, or do you prefer the external, globally accessible approach? Perhaps you’ve discovered a third path we haven’t explored yet. I’d really love to hear (or read). Thank you.