Home > Software design >  React native rerenders whole component on modal open/close
React native rerenders whole component on modal open/close

Time:02-24

My problem

I render a list of simple items (numbers or strings) using Array.map method. I use Modal to add/update items. However, every opening or closing of the modal makes react rerender the whole array, even if the array remains unmodified. I feel this is an expected behavior.

Questions

  1. Is it possible not to rerender the whole component when opening or closing the modal?
  2. What is the common way to add/update new items in array without rerendering the entire list?

Thanks, guys

Minimal code example

/* Console output:
 * ---------------
 * ROOT: render component
 * -> ITEM: render 1
 * -> ITEM: render 2
 * -> ITEM: render 3 (not in map)
 * ROOT: open modal
 * ROOT: render component
 * -> ITEM: render 1
 * -> ITEM: render 2
 * -> ITEM: render 3 (not in map)
 * MODAL: close Modal
 * ROOT: render component
 * -> ITEM: render 1
 * -> ITEM: render 2
 * -> ITEM: render 3 (not in map)
*/
import * as React from 'react';
import {useState} from 'react';
import {View, Text, Modal, Pressable, StyleSheet} from 'react-native';

const items = [1, 2];

const Item = ({el}) => {
  console.log(`-> ITEM: render ${el}`);
  return <Text>Item: {el}</Text>;
};

const Testing = () => {
  const [visible, setVisible] = useState(false);
  const openModal = () => {
    console.log('ROOT: open modal');
    setVisible(true);
  };

  console.log("ROOT: render component");

  return (
    <View style={styles.wrapper}>

      {/* Render simple list */}
      <Text style={styles.header}>All items:</Text>
      {items.map(el => (
        <Item el={el} key={el} />
      ))}
      <Item el={'3 (not in map)'} />

      {/* Button to open modal */}
      <Pressable style={styles.button} onPress={openModal}>
        <Text style={styles.header}>Tap me to open modal</Text>
      </Pressable>

      {/*The Modal*/}
      <Modal
        animationType="slide"
        transparent={false}
        visible={visible}
        onRequestClose={() => {
          console.log('MODAL: close Modal');
          setVisible(false);
        }}>
        <Text style={styles.header}>No content here...</Text>
      </Modal>
    </View>
  );
};

const styles = StyleSheet.create({
  wrapper: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'center',
  },
  button: {
    borderRadius: 5,
    backgroundColor: '#0aa',
    marginVertical: 10,
    padding: 10,
  },
  header: {
    fontSize: 18,
    fontWeight: '700',
  },
});

export default Testing;

CodePudding user response:

The reason it re-renders is because you are updating state (setVisible).

You can useRef.

useState is directly tied with a components lifecycle; useRef is not, meaning you can change it and it won't trigger a re-render.

Example:

       const visible = useRef()

       //your code

       useEffect(() => {
         visible.current = false  //initiate to false  or true 
         depending on what you need.
       }, [])

      <Modal
        animationType="slide"
        transparent={false}
        visible={visible}
        onRequestClose={() => {
          console.log('MODAL: close Modal');
          visible.current = !visible.current //will toggle between true and false
        }}>
        <Text style={styles.header}>No content here...</Text>
     </Modal>

CodePudding user response:

What I've learned today

Once we create a component, React is tracking its state and performs rerendering if the state changes. Rerendering includes all the children of the component. Thus, I can answer my questions.

Answer to question 1

Yes. We need visible is not in the state of the component that we wouldn't like to rerender. To achieve this, the Modal should be implemented separately from our component that turns into the parent of Modal.

At the same time, we want that parent can make set Modal visible. It is here that a new-for-me react hook comes into a play: useImperativeHandle.

The workflow is as follows. The parent forwards a reference ref to Modal using a forwardRef function that wrapps the Modal component. Then, Modal declares ref as a handler that can be used by parent. This declaration and available porperties are provided via useImperativeHandler hook. That's it.

Note, however, that imperative code using refs should be avoided in most cases, as official docs state.

Below is the code snippet as a memento for me. No rerender on Modal open/close at this time!

/* Console output:
 * ---------------
 * ROOT: render component
 * -> ITEM: render 1
 * -> ITEM: render 2
 * -> ITEM: render 3 (not in map)
 * ROOT: open modal
 * MODAL: openMe called from parent component via ref
 * MODAL: close Modal
 */

//
// Modal component
//
const _MyModal = (props, ref) => {
  const [visible, setVisible] = useState(false);

  const openMe = () => {
    console.log('MODAL: openMe called from parent component via ref');
    setVisible(true);
  };

  useImperativeHandle(ref, () => ({publicHandler: openMe}}), [openMe]));

  return (<Modal>...</Modal>);
};

const MyModal = forwardRef(_MyModal);

//
// Testing component
//
const Testing = () => {
  const modalRef = useRef(null);

  const openModal = () => {
    console.log('ROOT: open modal');
    modalRef.current.publicHandler();
  };
  
  // Rest of code without <Modal> tag
}

Answer to question 1 using redux

If you use redux, there is no need in useImperativeHandle hook. Just connect Modal component to store to share visible and action creator actSetVisible, while connect the parent component to share only this action creator actSetVisible. Everything works similarly as illustrated above.

However, if you do want to use useImperativeHandle, you should point redux to the fact that you're forwarding a reference, when connecting Modal to store:

const MyModal = connect(mapS2P, mapD2P, null, {forwardRef: true}))(forwardRef(_MyModal));

Answer to question 2

The suggestions from above showed how to get rid of excessive rerendering when modal opens or closes. My second question implied that I do something newbily wrong in react because I'm really a newby here. Therefore, in my humble opinion, using Modals is a good way to add elements to lists. In my case, it was an array of composed components rather than an array of simple strings, and rerendering was a critical issue.

Afterword

Live a century, learn a century, and you'll die a fool. Hope, this will help to anyone.

  • Related