SourceSnippet
Come. Copy. Go. As simple as that!


thumbnail

Avoid Rerendering on changing sibling component in React.js & React Native

By Manas R. Makde
Posted: 20 November 2023

Traditional method (Top part of gif)

import { useRef, useState } from "react";
import { View, StyleSheet, Button, Text } from "react-native";


const CustomButton = (props) => {

  const renderCounter = useRef(0);
  renderCounter.current = renderCounter.current + 1;

  return (<View style={styles.button_wrapper}>
    <Button {...props} title="Toggle Color" color={"mediumseagreen"} />
    <Text style={styles.render_text}>Render Count: {renderCounter.current}</Text>
  </View>)
}


const CustomItem = ({ backColor }) => {

  const renderCounter = useRef(0);
  renderCounter.current = renderCounter.current + 1;

  return (<View style={styles.item}>
    <Text style={[{ backgroundColor: backColor }, styles.item_text]}>Sibling Component</Text>
    <Text>Render Count:{renderCounter.current}</Text>
  </View>)
}


export default function App() {

  const [backColor, setBackColor] = useState("crimson")

  return (<View style={styles.body}>
    <CustomButton onPress={() => { setBackColor(val => val == "crimson" ? "dodgerblue" : "crimson") }} />
    <CustomItem backColor={backColor} />
  </View>)
}


const styles = StyleSheet.create({
  body: {
    flex: 1,
    flexDirection: "row",
    alignItems: "center",
    justifyContent: "center",
    backgroundColor:"ghostwhite"
  },
  button_wrapper: {
    width: "50%",
    margin: 10
  },
  item: {
    flex: 1,
    height: 80,
    margin: 10,
    borderRadius: 5,
    justifyContent: "center",
    alignItems: "center"
  },
  item_text: {
    height: 50,
    width: "100%",
    borderRadius: 5,
    color: "white",
    fontWeight: "bold",
    textAlign: "center",
    textAlignVertical: "center",
  },
  render_text: {
    textAlign: "center",
    marginTop: 5
  }
});
import { useRef, useState } from "react";


const CustomButton = (props) => {
  const renderCounter = useRef(0);
  renderCounter.current = renderCounter.current + 1;

  return (
    <div style={styles.button_wrapper}>
      <input type="button" {...props} value="Toggle Color"/>
      <div style={styles.render_text}> Render Count: {renderCounter.current} </div>
    </div>
  );
};


const CustomItem = ({ backColor }) => {
  const renderCounter = useRef(0);
  renderCounter.current = renderCounter.current + 1;

  return (
    <div style={styles.item}>
      <div style={{ backgroundColor: backColor, ...styles.item_text }}> Sibling Component </div>
      <div>Render Count:{renderCounter.current}</div>
    </div>
  );
};


export default function App() {
  const [backColor, setBackColor] = useState("crimson");

  return (
    <div style={styles.body}>
      <CustomButton onClick={() => { setBackColor((val) => (val == "crimson" ? "dodgerblue" : "crimson")) }} />
      <CustomItem backColor={backColor} />
    </div>
  );
}


const styles = {
  body: {
    display: "flex",
    alignItems: "center"
  },
  button_wrapper: {
    width: "50%",
    display: "flex",
    flexDirection: "column",
    margin: 10
  },
  item: {
    display: "flex",
    flexDirection: "column",
    flex: 1,
    margin: "10px",
    justifyContent: "center",
    alignItems: "center"
  },
  item_text: {
    width: "100%",
    padding: "0.5rem",
    borderRadius: 5,
    color: "white",
    textAlign: "center"
  },
  render_text: {
    textAlign: "center",
    marginTop: "10px"
  }
};

Optimized method (Bottom part of gif)

import { forwardRef, useImperativeHandle, useRef, useState } from "react";
import { View, StyleSheet, Button, Text } from "react-native";


const CustomButton = (props) => {

  const renderCounter = useRef(0);
  renderCounter.current = renderCounter.current + 1;

  return (<View style={styles.button_wrapper}>
    <Button {...props} title="Toggle Color" color={"mediumseagreen"} />
    <Text style={styles.render_text}>Render Count: {renderCounter.current}</Text>
  </View>)
}


const CustomItem = forwardRef((props, ref) => {

  const [backColor, setBackColor] = useState("crimson")
  const renderCounter = useRef(0);
  renderCounter.current = renderCounter.current + 1;

  useImperativeHandle(ref, () => ({
    toggleColor() {
      setBackColor(val => val == "crimson" ? "dodgerblue" : "crimson")
    }
    // or
    //
    // toggleColor: ()=>{
    //   setBackColor(val => val == "crimson" ? "dodgerblue" : "crimson")
    // }

  }), []);

  return (<View style={styles.item}>
    <Text style={[{ backgroundColor: backColor }, styles.item_text]}>Sibling Component</Text>
    <Text>Render Count: {renderCounter.current}</Text>
  </View>)
})


export default function App() {

  const itemRef = useRef()

  return (<View style={styles.body}>
    <CustomButton onPress={() => { itemRef.current.toggleColor() }} />
    <CustomItem ref={itemRef} />
  </View>)
}


const styles = StyleSheet.create({
  body: {
    flex: 1,
    flexDirection: "row",
    alignItems: "center",
    justifyContent: "center",
    backgroundColor: "ghostwhite"
  },
  button_wrapper: {
    width: "50%",
    margin: 10
  },
  item: {
    flex: 1,
    height: 80,
    margin: 10,
    borderRadius: 5,
    justifyContent: "center",
    alignItems: "center"
  },
  item_text: {
    height: 50,
    width: "100%",
    borderRadius: 5,
    color: "white",
    fontWeight: "bold",
    textAlign: "center",
    textAlignVertical: "center",
  },
  render_text: {
    textAlign: "center",
    marginTop: 5
  }
});
import { forwardRef, useImperativeHandle, useRef, useState } from "react";


const CustomButton = (props) => {
  const renderCounter = useRef(0);
  renderCounter.current = renderCounter.current + 1;

  return (<div style={styles.button_wrapper}>
    <input type="button" {...props} value="Toggle Color" />
    <div style={styles.render_text}> Render Count: {renderCounter.current} </div>
  </div>);
}


const CustomItem = forwardRef((props, ref) => {
  const [backColor, setBackColor] = useState("crimson");
  const renderCounter = useRef(0);
  renderCounter.current = renderCounter.current + 1;

  useImperativeHandle(ref, () => ({
    toggleColor() {
      setBackColor(val => val == "crimson" ? "dodgerblue" : "crimson")
    }
    // or
    //
    // toggleColor: ()=>{
    //   setBackColor(val => val == "crimson" ? "dodgerblue" : "crimson")
    // }

  }), []);

  return (<div style={styles.item}>
    <div style={{ backgroundColor: backColor, ...styles.item_text }}> Sibling Component </div>
    <div>Render Count:{renderCounter.current}</div>
  </div>);
})


export default function App() {

  const itemRef = useRef()

  return (<div style={styles.body}>
    <CustomButton onClick={() => { itemRef.current.toggleColor() }} />
    <CustomItem ref={itemRef} />
  </div>);
}


const styles = {
  body: {
    display: "flex",
    alignItems: "center"
  },
  button_wrapper: {
    width: "50%",
    display: "flex",
    flexDirection: "column",
    margin: 10
  },
  item: {
    display: "flex",
    flexDirection: "column",
    flex: 1,
    margin: "10px",
    justifyContent: "center",
    alignItems: "center"
  },
  item_text: {
    width: "100%",
    padding: "0.5rem",
    borderRadius: 5,
    color: "white",
    textAlign: "center"
  },
  render_text: {
    textAlign: "center",
    marginTop: "10px"
  }
};

References:

react-nativereactjs