One key factor for positive user experience is speed i.e how much time a user has to wait to first see contents from your website or application. Amazon reports a 1% sales loss for every 100ms load time, Walmart reports a +2% conversion per one second load time improvement.
Negative statistics lead to a decrease in user satisfaction and eventually customers. One solution to mitigating this is to properly optimize your application.
In computer science, optimization is the selection of the best element (with regard to some criterion) from some set of available alternatives.
React allows us to build encapsulated components that manage their own state, then composes them to make complex UIs.
These components make up small parts of our UI. This means a lot of times we unintentionally create redundant components and structure our code in ways that can impact the overall loading time of our application.
As stated earlier, the solution is optimization, and in this case, we can describe it as the best way we can write a particular code block, function or component to achieve re-usability and reduce the time taken to return information mostly in a near instant manner.
With the help of some inbuilt APIs like React.Component
, React.PureComponent
and life-cycle methods React offers, we can optimize our components to ensure fast and efficient load times in our applications.
Typically, our components are composed of CSS, JS and HTML code. Being able to determine when certain pieces show up will have a great impact on your page speed.
In this tutorial, we will learn various optimization methods that use these inbuilt APIs, lifecycle methods and some other general techniques that ensure you improve your React code.
A basic understanding of React is assumed in this tutorial. You can learn more about React here.
At some point in your application, you will need to return multiple elements. From a table list to a group of related texts you will definitely reach scenarios where you need to return a group of data.
Your code will look something like this:
// Parent.js class Parent extends React.Component { render() { return ( <h1>Hello there!</h1> <h1>Hello there again!</h1> ) } }
If you are using a linter you will see the error: JSX parent expressions must have one parent element
you will be forced to wrap both elements in a parent element aka div like so:
<div> <h1>Hello there!</h1> <h1>Hello there again!</h1> </div>
Even though everything works fine, an extra unnecessary div is created. This can lead to so many useless elements being created around our application and can also cause invalid HTML in some cases where our render data is coming from a child component in a specific order. Consider the following code:
// Table.js class Table extends React.Component { render() { return ( <table> <tr> <Columns /> </tr> </table> ); } } class Columns extends React.Component { render() { return ( <div> <td>column one</td> <td>column two</td> </div> ); } }
The above code will render the following in our table component:
<table> <tr> <div> <td>column one</td> <td>column two</td> </div> </tr> </table>
This is definitely not the intended output as it is an invalid HTML syntax. Fragment solves this for you. We can re-write our column component to:
// columns.js class Columns extends React.Component { render() { return ( <React.Fragment> <td>column one</td> <td>column two</td> </React.Fragment> ); } }
Now you get the intended output and even better no extra DOM node is created. This may seem small but in reality the more elements on a page the more time it will take to load. Therefore auditing pieces of your code and updating them to use fragments to group data where necessary will definitely improve your code and it’s performance. Find out more about fragments here.
Typically, you want to load parts of your app only when they are requested. For instance, loading shopping cart data only when the cart icon is clicked, loading images at the bottom of a long image list when the user scrolls to that point, etc.
Lazy loading is a popular optimization technique widely used to speed up the load time of applications.
React.Lazy
helps us load components on demand thereby reducing the load time of our application as only the needed pieces will be present as requested.
With its simple syntax, it can be used easily without hassle. It takes a callback function as a parameter that will load the component’s file using the dynamic import()
syntax.
// MyComponent.js class MyComponent extends Component{ render() { return (<div>MyComponent</div>) } } const MyComponent = React.lazy(()=>import('./MyComponent.js')) function App() { return (<div><MyComponent /></div>) }
Behind the scenes, at compile time a separate bundle is created by our webpack when it hits the React.lazy()
and import()
statement. This process is called Code-Splitting. Our final app will be separated into multiple bundles containing UI pieces that would be loaded whenever they are required.
In the time the component will be swapped in, a small time lag will occur leaving a screen freeze experience for your user. To give the user update or feedback on the outcome of the process React.Suspense
is used.
React.Suspense
is used to wrap lazy components to show fallback content while loading the component.
// MyComponent.js const Mycomponent = React.lazy(()=>import('./component.js')) function App() { return ( <div> <Suspense fallback={<div>loading ..</div>}> <MyComponent /> </Suspense> </div>) }
Now, whenever the component is being loaded and there’s a delay a fallback text loading. . will be rendered. Find out more about React.Suspense
and .Lazy
here.
Most times in our application, we end up having instances of one component present on the screen. For example, on a blog page, we might have different blog posts show up via a blog post component that in turn also renders like button components. If not properly managed, a change in the state of a button component can cause all of the button components to re-render. The solution to this is using shouldComponentUpdate
method.
shouldComponentUpdate()
is used to let React know if a component’s output is not affected by the current change in state or props. By default, it re-renders on every state change. It always returns a boolean as a response — if the component should re-render or not. The default is that it always returns true.
A shouldComponentUpdate method is called with nextProps as the first argument and nextState as the second:
shouldComponentUpdate(nextProps, nextState){ return nextProps.next !== this.props.next }
Now, if the next prop hasn’t changed there’s no reason to change the appearance of the component by re-rendering. This may not seem like huge improvements, however, in an application with so many components re-rendering shouldComponentUpdate
will help improve performance.
N/B:
shouldComponentUpdate
can cause unintended problems if you set it and forget , because your React component will not update normally. Consider using the built-in PureComponent instead of writingshouldComponentUpdate()
by hand.
Instead of using shouldComponentUpdate
method in our components, React introduced a new component with built-in shouldComponentUpdate
implementation, the React.PureComponent
component.
React.PureComponent
is similar to React.Component. The difference between them is that React.Component doesn’t implement shouldComponentUpdate(), but React.PureComponent
implements it with a shallow prop and state comparison.
No extra code is needed, all you need to do is to use it in your class declaration:
// use this class MyComponent extends React.PureComponent{ render() { return (<div>MyComponent</div>) } } // instead of class MyComponent extends React.Component{ render() { return (<div>MyComponent</div>) } }
Now with our pure component we no longer have to write:
shouldComponentUpdate(nextProps, nextState){ return nextProps.next !== this.props.next }
It already implements this by default for us.
Although this is the recommended way to use shouldComponentUpdate only extend PureComponent
when you expect to have simple props and state, or use forceUpdate() when you know deep data structures have changed. Or, consider using immutable objects to facilitate fast comparisons of nested data. Find out more here.
When working with React it is important to think of what happens when an element is removed from the DOM. Do they really go away? Or does the code just lie around even though it is not displayed to the user?
Having unused code around causes a problem called memory leak. React solves this for us by providing us with the componentWillUnmount
method.
componentWillUnmount()
is used to stop any unused code from running when a component is removed from the DOM.
You can perform several cleanups with this method, such as invalidating timers, canceling network requests, or cleaning up any subscriptions that were created in componentDidMount()
.
Consider the following component:
// App.js class App extends Component { constructor(props) { super(props); this.state = {}; } componentDidMount() { document.addEventListener("click", this.closeMenu); } openMenu = () => { } closeMenu = () => { } render() { return ( <a href ="#" onClick = {this.closeMenu}>X</a> ); } }
In the above code, when you click the X link without the componentDidUnmount()
the menu is closed but the event listener which was created when the component was mounted is still available.
To fix that, we can add a componentDidUnmount()
to our component:
componentWillUnmount() { document.removeEventListener("click", this.closeMenu); }
Now, when the button is clicked the event listener is removed with the componentDidUnmount() method.
N/B: You should not call setState() in
componentWillUnmount()
because the component will never be re-rendered. Once a component instance is unmounted, it will never be mounted again. Learn more about it here.
One way to speed up an application is by implementing memoization.
Memoization is an optimization technique used to primarily speed up programs by storing the results of expensive function calls and returning the cached results when the same inputs occur again.
A memoized function is faster because if the function is called with the same values as the previous one instead of executing function logic, it will instead fetch the result from cache.
In React, it is not uncommon for a component to change state multiple times. It is also not uncommon for some components to exist without the need to change state. If you have several components that rarely change state you should consider caching them.
React.Memo
provides us with an easy API to implement memoization. It became available in React V16.6.0. Consider the following component:
// userDetails.js const UserDetails = ({user}) =>{ const {name, occupation} = user; return ( <div> <h4>{name}</h4> <p>{occupation}</p> </div> ) }
Currently, every time the userDetails function is called it executes the function over and over again even if these details rarely change. We can use React.memo to cache it:
export default React.memo(UserDetails)
That’s all! As you can see no complex code is required. Simply wrap your component in the React.memo function and React takes care of the rest for you.
React.memo is a higher order component. It’s similar to React.PureComponent but for function components instead of classes. Find out more here.
In this tutorial, we explored several ways we can optimize our React components for better performance. We discussed only a few as there are lots of ways and tools used when optimizing an application.
Optimizing applications should be as needed because in some simple scenarios optimizing your components might be a killer.
One thing to keep in mind is the size and complexity of your project, would it be used for just demo or simple use cases? Or would it be deployed to be used every day by people? If the latter is the case then it may be time to think about optimizing your application. You can find out more about how React handles optimization internally here. Got questions or know other efficient ways to optimize applications? Let’s talk in the comments. Happy coding!
Install LogRocket via npm or script tag. LogRocket.init()
must be called client-side, not
server-side
$ npm i --save logrocket // Code: import LogRocket from 'logrocket'; LogRocket.init('app/id');
// Add to your HTML: <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script> <script>window.LogRocket && window.LogRocket.init('app/id');</script>
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowDesign React Native UIs that look great on any device by using adaptive layouts, responsive scaling, and platform-specific tools.
Angular’s two-way data binding has evolved with signals, offering improved performance, simpler syntax, and better type inference.
Fix sticky positioning issues in CSS, from missing offsets to overflow conflicts in flex, grid, and container height constraints.
From basic syntax and advanced techniques to practical applications and error handling, here’s how to use node-cron.