Hi everyone! There is a lot of information about the various features of React. About various life hacks and best practices in this framework. I want to talk about how to write code more efficiently and use React to the maximum.
React.PureComponent
In React, function components and PureComponent
provide two different ways of optimizing React apps at the component level.
Function components prevent constructing class instances while reducing the overall bundle size as it minifies better than classes. On the other hand, in order to optimize UI updates, we can consider converting function components to a PureComponent
class (or a class with a custom shouldComponentUpdate
method).
However, if the component doesn’t use state and other life cycle methods, the initial render time is a bit more complicated when compared to function components with potentially faster updates.
React.PureComponent
does a shallow comparison on state change. This means it compares values when looking at primitive data types, and compares references for objects. Due to this, we must make sure two criteria are met when using React.PureComponent
:
All child components of React.PureComponent
should also be a Pure or functional component.
Memoization is an optimization technique used primarily to speed up computer programs by storing the results of expensive function calls and returning the cached result when the same inputs occur again. A memoized function is usually faster because if the function is called with the same values as the previous one then instead of executing function logic it would fetch the result from cache.
Let's consider below simple stateless UserDetails
React component.
const UserDetails = ({user, onEdit}) => {
const {title, full_name, profile_img} = user;
return (
<div className="user-detail-wrapper">
<img src={profile_img} />
<h4>{full_name}</h4>
<p>{title}</p>
</div>
)
}
Here, all the children in UserDetails
are based on props. This stateless component will re-render whenever props changes. If the UserDetails
component attribute is less likely to change, then it's a good candidate for using the memoize version of the component:
const UserDetails = ({user, onEdit}) => {
const {title, full_name, profile_img} = user;
return (
<div className="user-detail-wrapper">
<img src={profile_img} />
<h4>{full_name}</h4>
<p>{title}</p>
</div>
)
}
export default React.memo(UserDetails)
This method will do a shallow equal comparison of both props and context of the component based on strict equality.
React just added a new function called react.lazy()
. It is a new function that allows you to load react components lazily through code splitting without help from any additional libraries. Lazy loading is the solution of rendering only needed or critical user interface items first, then quietly unrolling the non-critical items later. There is also a built-in component available called Suspense
that is required by the lazy function. It’s used for wrapping lazy components. It takes a fallback property that accepts the react elements you want to render as the lazy component is being loaded. Great place to implement a spinner or loading message.
Here is a basic example of how it will look like:
const Tasks = lazy(() => import('./Tasks'))
class App extends React.Component {
render() {
return (
<div className="App">
<Suspense fallback={<h1>Loading…</h1>}>
<Tasks />
</Suspense>
</div>
);
}
}
Using the key as the index can show your app incorrect data as it is being used to identify DOM elements. When you push or remove an item from the list, if the key is the same as before, React assumes that the DOM element represents the same component.
It's always advisable to use a unique property as a key, or if your data doesn't have any unique attributes, then you can think of using the shortid module which generates a unique key.
import shortid from "shortid";
{
comments.map((comment, index) => {
<Comment
{..comment}
key={shortid.generate()} />
})
}
If the data has a unique property, such as an ID, then it's better to use that property.
Since functions are objects in JavaScript ({} !== {}
), the inline function will always fail the prop diff when React does a diff check. Also, an arrow function will create a new instance of the function on each render if it's used in a JSX property.
This might create a lot of work for the garbage collector.
default class CommentList extends React.Component {
state = {
comments: [],
selectedCommentId: null
}
render() {
const { comments } = this.state
return (
comments.map((comment)=> {
return <Comment onClick={(e)=> {
this.setState({selectedCommentId:comment.commentId})
}} comment={comment} key={comment.id}/>
})
)
}
}
Instead of defining the inline function for props, you can define the arrow function.
default class CommentList extends React.Component {
state = {
comments: [],
selectedCommentId: null
}
onCommentClick = (commentId)=> {
this.setState({selectedCommentId:commentId})
}
render() {
const { comments } = this.state;
return (
comments.map((comment)=> {
return <Comment onClick={this.onCommentClick}
comment={comment} key={comment.id}/>
})
)
}
}
You should avoid spreading properties into a DOM element as it adds unknown HTML attribute, which is unnecessary and a bad practice.
const CommentsText = props => {
return (
<div {...props}>
{props.text}
</div>
)
}
Instead of spreading props, you can set specific attributes:
const CommentsText = props => {
return (
<div specificAttr={props.specificAttr}>
{props.text}
</div>
)
}
This performance tip is similar to the previous section about anonymous functions. Object literals don’t have a persistent memory space, so your component will need to allocate a new location in memory whenever the component re-renders:
function ComponentA() {
return (
<div>
HELLO WORLD
<ComponentB style={{/* */}}
color: 'blue',
background: 'gold'
}}/>
</div>
);
}
function ComponentB(props) {
return (
<div style={this.props.style}>
TOP OF THE MORNING TO YA
</div>
)
}
Each time ComponentA
is re-rendered a new object literal has to be “created” in-memory.
Additionally, this also means that ComponentB
is actually receiving a different style object. Using memo
and PureComponent
won’t even prevent re-renders here This tip doesn’t apply to style props only, but it’s typically where object literals are unwittingly used in React components.
This can be easily fixed by naming the object (outside of the component’s body):
const myStyle = { //
color: 'blue',
background: 'gold'
};
function ComponentA() {
return (
<div>
HELLO WORLD
<ComponentB style={myStyle} />
</div>
);
}
function ComponentB(props) {
return (
<div style={this.props.style}>
TOP OF THE MORNING TO YA
</div>
)
}
react-window
When you want to render an enormous table or list of data, it can significantly slow down your app’s performance. Virtualization can help in a scenario like this with the help of a library like react-window
. react-window
helps solve this problem by rendering only the items in the list that are currently visible, which allows for efficiently rendering lists of any size.
import { FixedSizeList as List } from 'react-window'
const Row = ({ index, style }) => (
<div className={index % 2 ? 'ListItemOdd' : 'ListItemEven'} style={style}>
Row {index}
</div>
)
const Example = () => (
<List
className="List"
height={150}
itemCount={1000}
itemSize={35}
width={300}
>
{Row}
</List>
)
React.Fragments
to Avoid Additional HTML Element WrappersReact.fragments
lets you group a list of children without adding an extra node.
class Comments extends React.PureComponent {
render() {
return (
<React.Fragment>
<h1>Comment Title</h1>
<p>comments</p>
<p>comment time</p>
</React.Fragment>
)
}
}
Or you can do it like this:
class Comments extends React.PureComponent {
render() {
return (
<>
<h1>Comment Title</h1>
<p>comments</p>
<p>comment time</p>
</>
)
}
}
P.S. Thanks for reading!
More articles about frontend development: