React Native App Performance

Ajmal Hasan - Oct 25 '22 - - Dev Community

Github Url

React useCallback Hook

The React useCallback Hook returns a memoized callback function.

Think of memoization as caching a value so that it does not need to be recalculated.

This allows us to isolate resource intensive functions so that they will not automatically run on every render.

The useCallback Hook only runs when one of its dependencies update.

This can improve performance.

The useCallback and useMemo Hooks are similar. The main difference is that useMemo returns a memoized value and useCallback returns a memoized function.

One reason to use useCallback is to prevent a component from re-rendering unless its props have changed.

EXAMPLE WITH EXPLANATORY COMMENT:

import {StyleSheet, Text, TouchableOpacity, View} from 'react-native';
import React, {useCallback, useState} from 'react';

const App = () => {
  const [number, setnumber] = useState(0);

  // 1. Whenever set state is called all function that are not memoized get recreated
  // const onDecrement = () => {
  //   console.log('rerenders-', number);
  //   setnumber(number - 1);
  // };
  // const onIncrement = () => {
  //   console.log('rerenders+', number);
  //   setnumber(number + 1);
  // };

  // 2. If we dont pass dependency array next state updation wont happen and reflect, as this function does not get recreated
  // const onDecrement = useCallback(() => {
  //   console.log('rerenders-', number);
  //   setnumber(number - 1);
  // }, []);

  // 3. Since we have memoised the function and also passed dependency so this function will only be recreated whenever dependency data changes
  const onDecrement = useCallback(() => {
    console.log('rerenders-', number);
    setnumber(number - 1);
  }, [number]);

  const onIncrement = useCallback(() => {
    console.log('rerenders+', number);
    setnumber(number + 1);
  }, [number]);

  // 4. Since below function is not memoised this function will be recreated whenever any state changes
  const notMemoizedFunction = () => {
    console.log('rerendersNM', number);
  };

  return (
    <View style={styles.container}>
      <View style={{alignContent: 'center', alignItems: 'center'}}>
        <TouchableOpacity onPress={onDecrement}>
          <Text style={styles.textStyle}>-</Text>
        </TouchableOpacity>
        <Text style={styles.textStyle}>{number}</Text>
        <TouchableOpacity onPress={onIncrement}>
          <Text style={styles.textStyle}>+</Text>
        </TouchableOpacity>
      </View>
    </View>
  );
};

export default App;

const styles = StyleSheet.create({
  container: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'center',
  },
  textStyle: {
    fontSize: 20,
    fontWeight: 'bold',
  },
  alignCenter: {
    alignContent: 'center',
    alignItems: 'center',
  },
});
Enter fullscreen mode Exit fullscreen mode

React Memo

Using memo will cause React to skip rendering a component if its props have not changed.

This can improve performance.

EXAMPLE WITH EXPLANATORY COMMENT:
index.js

import {StyleSheet, Text, TouchableOpacity, View} from 'react-native';
import React, {useCallback, useState} from 'react';
import CounterComponent from './CounterComponent';

const App = () => {
  const [number, setnumber] = useState(0);
  const [randomNumber, setrandomNumber] = useState(0);

  const onDecrement = useCallback(() => {
    setnumber(number - 1);
  }, [number]);

  const onIncrement = useCallback(() => {
    setnumber(number + 1);
  }, [number]);

  // 1. Since randomNumber will always be random, so no need to add dependency as always different value is memoised
  // 2. On clicking onRandomNumber, child component ie "CounterComponent" also gets rerendered though we are not passing randomNumber state to it.
  // 3. In order to avoid it we wrap our child component with React.memo(ChildComponent), so that this component only gets rerendered when passed prop changes ie number.
  const onRandomNumber = useCallback(() => {
    setrandomNumber(Math.random());
  }, []);

  return (
    <View style={styles.container}>
      <View style={{alignContent: 'center', alignItems: 'center'}}>
        <Text onPress={onRandomNumber} style={styles.textStyle}>
          Random Number {randomNumber}
        </Text>
        <TouchableOpacity onPress={onDecrement}>
          <Text style={styles.textStyle}>-</Text>
        </TouchableOpacity>
        <CounterComponent number={number} />
        <TouchableOpacity onPress={onIncrement}>
          <Text style={styles.textStyle}>+</Text>
        </TouchableOpacity>
      </View>
    </View>
  );
};

export default App;
Enter fullscreen mode Exit fullscreen mode

CounterComponent.js

import {StyleSheet, Text, View} from 'react-native';
import React from 'react';

const CounterComponent = ({number = 0}) => {
  console.log('Is component rerendered', number);
  return <Text style={styles.textStyle}>{number}</Text>;
};

export default React.memo(CounterComponent);
Enter fullscreen mode Exit fullscreen mode

React useMemo Hook

The React useMemo Hook returns a memoized value.

Think of memoization as caching a value so that it does not need to be recalculated.

The useMemo Hook only runs when one of its dependencies update.

This can improve performance.

The useMemo and useCallback Hooks are similar. The main difference is that useMemo returns a memoized value and useCallback returns a memoized function.

The useMemo Hook can be used to keep expensive, resource intensive functions from needlessly running.

EXAMPLE WITH EXPLANATORY COMMENT:

import {StyleSheet, Text, TouchableOpacity, View} from 'react-native';
import React, {useCallback, useMemo, useState} from 'react';

const App = () => {
  const [number, setnumber] = useState(0);
  const [randomNumber, setrandomNumber] = useState(0);

  const onDecrement = useCallback(() => {
    //memoised function
    setnumber(number - 1);
  }, [number]);

  const onIncrement = useCallback(() => {
    //memoised function
    setnumber(number + 1);
  }, [number]);

  const onRandomNumber = useCallback(() => {
    //memoised function
    setrandomNumber(Math.random());
  }, []);

  const expensiveCalculation = num => {
    console.log('Calculating...');
    for (let i = 0; i < 100000000; i++) {
      num += 1;
    }
    return num;
  };

  //1. If we dont memoise it this resource extensive function will be called on any state update which will result in lag
  //const calculation = expensiveCalculation(number);
  //2. If we memoise it, useMemo function will only be called when dependency changes.
  const calculation = useMemo(() => expensiveCalculation(number), [number]);

  return (
    <View style={styles.container}>
      <View style={styles.alignCenter}>
        <Text onPress={onRandomNumber} style={styles.textStyle}>
          Random Number: {randomNumber}
        </Text>
        <TouchableOpacity onPress={onDecrement}>
          <Text style={styles.textStyle}>-</Text>
        </TouchableOpacity>
        <Text style={styles.textStyle}>Memoised Number: {calculation}</Text>
        <TouchableOpacity onPress={onIncrement}>
          <Text style={styles.textStyle}>+</Text>
        </TouchableOpacity>
      </View>
    </View>
  );
};

export default App;
Enter fullscreen mode Exit fullscreen mode

Remove Console Statements

Link

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .