Engineering Blog

                            

Common Anti-patterns with React

Nested components

When a component is defined inside another component, it creates a tight coupling between the two components, meaning that they are highly dependent on one another. This can make it difficult to reuse or re purpose the inner component. Also when the parent component re-renders , the inner component will be defined again during render. This means the child component gets a new memory address which can lead to performance issues.

import React, { useState } from "react";

export const Parent = () => {
  const [count, setCount] = useState(0);
  const handleClick = () => {
    setCount(count + 1);
  };

  const Child = () => {
    return (
      <div onClick={handleClick}>
        <span>i am child component</span>
      </div>
    );
  };
  return (
    <div>
      <span>I am parent component</span>
      <Child />
    </div>
  );
};

In the example above as we can see that it can be very intuitive to nest a component inside another component since the inner component does not need to be passed a prop to change the count as it falls inside the scope of the parent function and therefore it has access to the click handler function that changes the parent component’s count state.

A better way would be to move the inner function out of the parent function and pass the handler function as a prop.This will prevent the redefinition of the inner function/component.

import React, { useState } from "react";

const Child = ({changeCount}) => {
    return (
      <div onClick={changeCount}>
        <span>i am child component</span>
      </div>
    );
  };

export const Parent = () => {
  const [count, setCount] = useState(0);
  const handleClick = () => {
    setCount(count + 1);
  };

  return (
    <div>
      <span>I am parent component</span>
      <Child changeCount={handleClick}/>
    </div>
  );
};

Failing to memoize expensive functions

Let’s imaging a component that has two different pieces of state , and has an expensive function whose result is displayed on the screen. The Function depends on one of the states as an argument .Therefore every time the state changes the function will be called and result will be displayed. But if we somehow we change the other state, this will still result in the re-rendering of the component , and the function will be called with the previous state which has not change. This can cause performance bottleneck in our app.

import React, { useState, useMemo } from "react";

export const ExpensiveFxn = () => {
  const [count, setCount] = useState(0);
  const [unrelatedState, setUnrelatedState] = useState(100);

  // the fxn is called every time the component re-renders
  const expensiveFunction = (n) => {
    let result = 0;
    for (let i = 0; i < n; i++) {
      for (let j = 0; j < n; j++) {
        result += i * j;
      }
    }
    console.log("expesive function call", result);
    return result;
  };

  const clickHandler = () => {
    setCount(count + 1);

    // will change the dependent state , causes re-rendering of the component
    // will cause the expensive fxn to be called with updated state
  };
  const changeUnrelatedState = () => {
    setUnrelatedState(unrelatedState + 1);

    // will change non-dependent state, causes re-rendering of the component
    // will still cause calling of the expensive fxn , though it result will not change
  };

  const result = expensiveFunction(count);

  return (
    <div>
      <div>An Expesive fxn Result : {result}</div>
      <br></br>
      <div onClick={clickHandler}>Click here to change Result</div>
      <br></br>
      <br></br>

      <div onClick={changeUnrelatedState}>
        Click here to change unrelated state to re-render component
      </div>
      <br></br>
      <div>Unrelated State: {unrelatedState}</div>
    </div>
  );
};

A better way would be to memoize the result using useMemo hook . The useMemo hook will remember the last calculate value and only recall the function if the dependencies of the useMemo change. The function will not be recalled if some other state change causes the component to re-render as that state is not added as a dependency to useMemo.

import React, { useState, useMemo } from "react";

export const ExpensiveFxn = () => {
  const [count, setCount] = useState(0);
  const [unrelatedState, setUnrelatedState] = useState(100);

  // the fxn is called every time the component re-renders
  const expensiveFunction = (n) => {
    let result = 0;
    for (let i = 0; i < n; i++) {
      for (let j = 0; j < n; j++) {
        result += i * j;
      }
    }
    console.log("expesive function call", result);
    return result;
  };

  const clickHandler = () => {
    setCount(count + 1);

  };
  const changeUnrelatedState = () => {
    setUnrelatedState(unrelatedState + 1);
 
  };

  const result = useMemo( ()=>{
   return expensiveFunction(count)
  },[count]);

  return (
    <div>
      <div>An Expesive fxn Result : {result}</div>
      <br></br>
      <div onClick={clickHandler}>Click here to change Result</div>
      <br></br>
      <br></br>

      <div onClick={changeUnrelatedState}>
        Click here to change unrelated state to re-render component
      </div>
      <br></br>
      <div>Unrelated State: {unrelatedState}</div>
    </div>
  );
};

Useless divs

One of the most common problems that react developers run into is returning sibling elements from a component. A component in react must have a root element , in other words a component cannot return siblings and they must be wrapped by a root element. Since we need a wrapper for siblings , we often fall into trap of using div as a wrapper element . For example when wrapping list elements with div , inside an unordered list, it will cause issues with the CSS and the look and feel of the list. A <ul> tag is supposed to have <li> tags as direct children to work properly. Also wrapping with div will result in lot of redundant and useless divs in our code base.

import React from "react";

const ListComponent = () => {
  return (
    <div>
      <li>Go lang</li>
      <li>PHP</li>
      <li>nodejs</li>
    </div>
  );
};

export const List = () => {
  return (
    <div>
      <h1>List Backend Programming Languages</h1>

      <ul>
        <ListComponent />
      </ul>
    </div>
  );
};

Instead of wrapping with a div, we can wrap the sibling elements with a React fragment. A React fragment is just a wrapper that acts as a root element which do not add extra nodes on the DOM.

import React from "react";

const ListComponent = () => {
  return (
    <React.Fragment>
      <li>Go lang</li>
      <li>PHP</li>
      <li>nodejs</li>
    </React.Fragment>

    // or

//     <>
//     <li>Go lang</li>
//     <li>PHP</li>
//     <li>nodejs</li>
//   </>

  );
};

export const List = () => {
  return (
    <div>
      <h1>List Backend Programming Languages</h1>
      <ul>
        <ListComponent />
      </ul>
    </div>
  );
};

Bundle size

One of the major problems with large code bases in react is that after the build they tend to have huge bundle size. This means the browser has to download a huge chunk of Javascript from server before it can render/load the project. This causes slow initial page load that degrades the user experience.

In the image below we can see a screen snip of developer tools that show a list of different contents that is downloaded while loading the page which also includes a single main chunk of javascript named main.c2689540.chunk.js.

React.Lazy is function in react that lets you load react components lazily through code splitting without help from any additional libraries. Lazy loading is the technique of rendering only-needed or critical user interface items first, then quietly unrolling the non-critical items later. It takes a function that must call a dynamic import().

Suspense is a component required by the lazy function basically used to wrap lazy components. Multiple lazy components can be wrapped with the suspense component. It takes a fallback property that accepts the react elements you want to render as the lazy component is being loaded.

import React, { Suspense } from 'react';

const OtherComponent = React.lazy(() => import('./otherComponent'));
const AnotherComponent = React.lazy(() => import('./anotherComponent'));

export function LazyLoadedComp() {
  return (
    <div>
      <Suspense fallback={<div>Loading...</div>}>
        <section>
          <OtherComponent />
          <AnotherComponent />
        </section>
      </Suspense>
    </div>
  );
}

As we can see in the above image, the main java script bundle is broken down into smaller chunks which improves the initial page load.

Conclusion

It is very easy to introduce anti-patterns into our code base unknowingly. One should always look for these kind of anti-patterns in their code if any and try to mitigate them by following best practices and keeping oneself updated and fully informed about the same.

References

Previous Post
Next Post