Speed
zhang kaiyv at Pexels

Optimization is the number one thing that is on the mind of every dev when building any software, especially web apps. JavaScript applications have included some impressive configurations and features. Here, I’ll review the features and tricks that will help you optimize your app’s performance.

Regardless of the specific patterns and methods, you use to optimize your code. It’s always important to keep your code DRY. Always strive to reuse components — that’s guaranteed to help in writing optimized code. If you spend more time writing excellent code and less time re-writing mediocre code (with a much higher chance for errors) — beautiful things will happen.

Reusing code can become a real challenge when dealing with large code bases or with different repositories, and that’s for two main reasons:

  • You’re often unaware of useful pieces of code.
  • The traditional way of sharing code across repositories is through packages, and that requires some dense configurations.

A great way to solve this is by using tools like Bit (GitHub). You can isolate components from your codebase and share them on bit.dev

You can search for components on bit.dev easily utilizing filtering, and the live playground.

The useMemo() Function

This is a React hook that is used to cache functions in React, CPU-expensive functions.

function App() {
    const [count, setCount] = useState(0)
    
    const expFunc = (count)=> {
        waitSync(3000);
        return count * 90;
    }    const resCount = expFunc(count)    return (
        <>
            Count: {resCount}
            <input type="text" onChange={(e)=> setCount(e.target.value)} placeholder="Set Count" />
        </>
    )
}

This is an expensive function that will take three minutes to execute.

We have a variable resCountthat calls the expFunc with the count variable from the useState hook. We have an input that sets the count state whenever we type anything.

Whenever we type anything, our app component is re-rendered, causing the expFuncfunction to execute. If we type continuously, the function executes, causing a massive performance bottleneck. For each input, it will take three minutes for it to render. It shouldn’t run again after the first input because it’s the same. What should happen is the result is stored somewhere and it’s returned without running the function again.

Here, we will employ the useMemo hook to optimize the expFunc for us. useMemo has the structure:

useMemo(()=> func, [input_dependency])

The func in this example is the function we want to cache/memoize. The input_dependency is an array of inputs to func that the useMemo will cache against. If they change the func will be called.

Here is the updated example with useMemo on our functional component:

function App() {
    const [count, setCount] = useState(0)
    
    const expFunc = (count)=> {
        waitSync(3000);
        return count * 90;
    }    const resCount = useMemo(()=> {
        return expFunc(count)
    }, [count])    return (
        <>
            Count: {resCount}
            <input type="text" onChange={(e)=> setCount(e.target.value)} placeholder="Set Count" />
        </>
    )
}

Virtualizing Long Lists

If you render large lists of data, I recommend that you provide only a small portion of the datasets at a time within the visible viewport of a browser. The data is presented as the list scrolls. This technique is called “windowing.” Several quality React libraries are available for this. For example, here is react-window and react-virtuaized by Brian Vaughn.

React.PureComponent

React.PureComponent is a base component class which checks the fields of state and props to determine whether or not the component should be updated.

Here we will take a shouldComponentUpdate example and write it using React.PureComponent.

Here’s the original:

class ReactComponent extends Component {
    constructor(props, context) {
        super(props, context)
        this.state = {
            data: null
        }
        this.inputValue = null
    }    handleClick = () => {
        this.setState({data: this.inputValue})
    }    onChange = (evt) => {
        this.inputValue = evt.target.value
    }    shouldComponentUpdate( nextProps,nextState) {
        if(nextState.data === this.state.data)
            return false
        return true
    }    render() {
        l("rendering App")
        return (
            <div>
                {this.state.data}
                <input onChange={this.onChange} />
                <button onClick={this.handleClick}>Click Me </button>
            </div>
        )
    }
}

Here is the above example using React.PureComponent.

class ReactComponent extends React.PureComponent {
    constructor(props, context) {
        super(props, context)
        this.state = {
            data: null
        }
        this.inputValue = null
    }    handleClick = () => {
        this.setState({data: this.inputValue})
    }    onChange = (evt) => {
        this.inputValue = evt.target.value
    }    render() {
        l("rendering App")
        return (
            <div>
                {this.state.data}
                <input onChange={this.onChange} />
                <button onClick={this.handleClick}>Click Me </button>
            </div>
        )
    }
}

We took out the shouldComponentUpdate and instead made ReactComponent exctend React.PureComponent.

Repeatedly enter the same information in the textbox we will see that the ReactComponent will not render more than once.

This utilizes a method of shallowly comparing the fields of the previous props and state objects with the fields of the next props and state objects.

Function Caching

As you are aware, functions can be called in the the render method of the React component.

function expensiveFunc(input) {
    ...
    return output
}class ReactCompo extends Component {
    render() {
        return (
            <div>
                {expensiveFunc}
            </div>
        )
    }
}

If the function is expensive, it can delay the render while it waits for the function to finish executing.

In the above example, expensiveFunc is rendered in the JSX. Each subsequent render of the component calls the function to be executed again.

One solution is to cache the output so each execution gets faster as the same inputs occur.

function expensiveFunc(input) {
    ...
    return output
}const memoizedExpensiveFunc = memoize(expensiveFunc)class ReactCompo extends Component {
    render() {
        return (
            <div>
                {memoizedExpensiveFunc}
            </div>
        )
    }
}

Reselect Selectors

Using reselect optimizes our Redux state management (if you’re using Redux).

Redux utilizes immutability. New object references will be created every time on action dispatch. This will hamper performance because re-render will be triggered on components even when the object references change but the fields haven’t changed.

Reselect library encapsulates the Redux state, checks the fields of the state and informs React whether to render or not.

Reselect saves precious time by shallowly traversing through the previous and current Redux state fields to determine if they have changed despite having different memory references. If the fields change it tells React to re-render, if none of the fields have changed it cancels the re-render despite a new state object being created.

Web Workers

JavaScript code runs on a single thin thread. A long process on the same thread seriously affects the User Interface rendering code. The best bet is to move the process to another thread. You can do this with the help of Web workers. They allow us to create a thread and run it parallel to the main thread without hampering the flow.

// webWorker.js
const worker = (self) => {
    function generateBigArray() {
        let arr = []
        arr.length = 1000000
        for (let i = 0; i < arr.length; i++)
            arr[i] = i
        return arr
    }    function sum(arr) {
        return arr.reduce((e, prev) => e + prev, 0)
    }    function factorial(num) {
        if (num == 1)
            return 1
        return num * factorial(num - 1)
    }    self.addEventListener("message", (evt) => {
        const num = evt.data
        const arr = generateBigArray()
        postMessage(sum(arr))
    })
}
export default worker// App.js
import worker from "./webWorker"import React, { Component } from 'react';
import './index.css';class App extends Component {
    constructor() {
        super()
        this.state = {
            result: null
        }
    }
    calc = () => {
        this.webWorker.postMessage(null)
    }    componentDidMount() {
        let code = worker.toString()
        code = code.substring(code.indexOf("{") + 1, code.lastIndexOf("}"))
        const bb = new Blob([code], { type: "application/javascript" });
        this.webWorker = new Worker(URL.createObjectURL(bb))
        this.webWorker.addEventListener("message", (evt) => {
            const data = evt.data
            this.setState({ result: data })
        })
    }    render() {
        return ( 
            <div>
                <button onClick = { this.calc }> Sum </button>   
                <h3> Result: { this.state.result }</h3>  
            </div>
        )
    }
}

This calculates the sum of the array with one million elements. If this were done in the main thread, it hangs till the one million elements are traversed and their sum computed.

Our main thread runs smoothly parallel to the web worker thread. The sum of the element-array is computed. The result is then communicated when done and the main thread will only render the result. Quick, simple and highly performant.

Lazy Loading

Lazy loading has come to be one of the most popular optimization techniques to speed up the load time.

If you wish to lazy load route components in React, the React.lazy() API is used.

React.lazy makes it easy to create components and render them using dynamic imports.

React.lazy takes a function as a parameter:

React.lazy(()=>{})

// or

function cb () {}
React.lazy(cb)

This callback function must load the component’s file using the dynamic import():

// MyComponent.js
class MyComponent extends Component{
    render() {
        return <div>MyComponent</div>
    }
}const MyComponent = React.lazy(()=>{import('./MyComponent.js')})
function AppComponent() {
    return <div><MyComponent /></div>
}
// orfunction cb () {
    return import('./MyComponent.js')
}
const MyComponent = React.lazy(cb)
function AppComponent() {
    return <div><MyComponent /></div>
}

The callback function of the React.lazy returns a Promise via the import() call. The Promise resolves if the module loads successfully and rejects if there was an error in loading the module, due to network failure, wrong path resolution, no file found, etc.

When Webpack walks through our code to compile and bundle, it creates a separate bundle if it hits the React.lazy() and import().

Our app will become like this:

react-app
 dist/
  - index.html
  - main.b1234.js (contains Appcomponent and bootstrap code)
  - mycomponent.bc4567.js (contains MyComponent)/** index.html **/
<head>
    <div id="root"></div>
    <script src="main.b1234.js"></script>
</head>

Now, our app is separated into multiple bundles. When AppComponent gets rendered the mycomponent.bc4567.js file is loaded and the containing MyComponent is displayed on the DOM.

React.memo()

Just like useMemo and React.PureComponent, you can use React.memo() is used to memoize/cache functional components.

function My(props) {
    return (
        <div>
            {props.data}
        </div>
    )
}
function App() {
    const [state, setState] = useState(0)
    return (
        <>
            <button onClick={()=> setState(0)}>Click</button>
            <My data={state} />
        </>
    )
}

App renders My component passing the state to My via the data prop. Now, see the button sets the state to 0 when pressed. If the button is pressed continuously, the state remains the same throughout. My component would still be re-rendered despite the state passed to its prop being the same. This results in a performance bottle if there are thousands of components under App and My.

We’ll fix this by wrapping the My component with React.memo which will return a memoized version of My, that will be used in App.

function My(props) {
    return (
        <div>
            {props.data}
        </div>
    )
}
const MemoedMy = React.memo(My)function App() {
    const [state, setState] = useState(0)
    return (
        <>
            <button onClick={()=> setState(0)}>Click</button>
            <MemeodMy data={state} />
        </>
    )
}

Now pressing the button Click continuously will only trigger re-rendering in My once and never again. This is because React.memo will memoize its props and will return the cached output without executing the My component so far as the same inputs occur over and over.

What React.PureComponent is to class components is what Reat.memo is to functional components.

useCallback()

This works as useMemo but the difference is that it’s used to memoize function declarations.

function TestComp(props) {
    l('rendering TestComp')
    return (
        <>
            TestComp
            <button onClick={props.func}>Set Count in 'TestComp'</button>
        </>
    )
}
TestComp = React.memo(TestComp)function App() {
    const [count, setCount] = useState(0)    return (
        <>
            <button onClick={()=> setCount(count + 1)}>Set Count</button>
            <TestComp func={()=> setCount(count + 1)} />
        </>
    )
}

We have an App component that maintains a count state using useState. If we call the setCount function the App component renders again. It renders a button and the TestComp component. When we click the Set Count button the App component will re-render along with its child tree.

TestComp is memoized using memo to avoid unnecessary renders. React.memo memoizes a component by comparing its current/next props with its previous props. If they are the same it won’t render the component. TestComp receives a prop in a func props attribute. Whenever App is rendering, the props func of TestComp will be checked for sameness, if found being the same it will not be rendered.

The problem here is that TestComp receives a new instance of the function prop. How? Look at the JSX:

...
    return (
        <>
            ...
            <TestComp func={()=> setCount(count + 1)} />
        </>
    )
...

An arrow function declaration is passed, so whenever App is rendered a new function declaration is always created with a new reference(memory address pointer). So the shallow comparison of React.memo will record a difference and will give a go-ahead for re-rendering.

Now, how do we solve this problem? Should we move the function outside of the function scope, it will be good but it won’t have reference to the setCount function. This is where useCallback comes in, we will pass the function-props to the useCallback and specify the dependency, the useCallback hook returns a memoized version of the function-prop that’s what we will pass to TestComp.

function App() {
    const check = 90
    const [count, setCount] = useState(0)
    const clickHndlr = useCallback(()=> { setCount(check) }, [check]);    return (
        <>
            <button onClick={()=> setCount(count + 1)}>Set Count</button>
            <TestComp func={clickHndlr} />
        </>
    )
}

shouldComponentUpdate()

A React app is composed of components, from the root component usually the “App” in App.js all the way down the spreading branches.

class ReactComponent extends Component {
    render() {
        return (
            <div></div>
        )
    }
}

These component trees define the parent-child relationship. When a piece of bound-data is updated in a component, that component and its children are subsequently re-rendered to make the change propagate throughout the sub-component tree. When a component is to be re-rendered, React compares its previous data (props and context) to its current data (props and context). If they are the same there is no re-render, but if there is a difference the component and its children are re-rendered.

React compares the differences by object reference using the strict equality operator === since the props and context are objects. So React uses the reference to know when the previous props and state has changed from the current props and state.

class ReactComponent extends Component {
    constructor(props, context) {
        super(props, context)
        this.state = {
            data: null
        }
        this.inputValue = null
    }

handleClick = () => {
        this.setState({data: this.inputValue})
    }

onChange = (evt) => {
        this.inputValue = evt.target.value
    }

render() {
        l("rendering App")
        return (
            <div>
                {this.state.data}
                <input onChange={onChange} />
                <button onClick={handleCick}>Click Me </button>
            </div>
        )
    }
}

See, the component above. It has data in the state object. If we enter a value in the input textbox and press the Click Me button, the value in the input will be rendered. I purposely added the l ("rendering App") in the render method so we know when the ReactComponent renders.

Now, if we enter 2 and click the button the component will be rendered.

It is supposed to be rendered because the previous state is this:

state = { data: null }

and the next state object is this:

state = { data: 2 }

setState creates a new state object every time it is called, the strict equality operator will see different memory references and trigger re-render on the component.

If we click on the button again, we will have another re-render. It shouldn’t because the previous state object and the next state object will have the same data value. However, because of setState’s new state object creation React will see different state object references and trigger re-render despite them being of the same internal value.

Now, this re-rendering might be expensive if the component tree grows to thousands of components.

React provides a way for us to control the re-rendering of our components instead of the internal logic by React. It is the shouldComponentUpdate method. This shouldComponentUpdate is called whenever the component is being re-rendered. If it returns true then the component is re-rendered, otherwise the re-rendering is canceled.

We will add the shouldComponentUpdate to the ReactComponent. This method accepts the next state object and next props object as an argument, so with this, we will implement our check to tell React when we wnt to re-render.

class ReactComponent extends Component {
    constructor(props, context) {
        super(props, context)
        this.state = {
            data: null
        }
        this.inputValue = null
    }
handleClick = () => {
        this.setState({data: this.inputValue})
    }
onChange = (evt) => {
        this.inputValue = evt.target.value
    }
shouldComponentUpdate( nextProps,nextState) {
        if(nextState.data === this.state.data)
            return false
        return true
    }
render() {
        l("rendering App")
        return (
            <div>
                {this.state.data}
                <input onChange={this.onChange} />
                <button onClick={this.handleClick}>Click Me </button>
            </div>
        )
    }
}

See, in the shouldCmponentUpdate, I checked the data value in the nextState object and the current state object. If they not equal it returns true which will trigger a re-render, if they are not equal it returns false which cancels the re-render.

Run the app again, type 2 and continuously click the Click Me button, you will see that the rendering happens once and no more 🙂

See, we used the shouldComponentUpdate method to set when our component will be re-rendered effectively boosting the performance of our component.

The tricks we mentioned should not all be implemented at the same time. Be judicious in their usage.

Remember, don’t optimize early, code the project first and then optimize where necessary.