skip to Main Content

I might have asked this question in a confusing way, so I apologize. But I have a map function that renders a lot of text elements dynamically and also styles them based on state. However, the calculations used to create the elements in the first place seems really expensive performance-wise. So I would like to render them once, and then store the created elements in state, and yet still have the styling rerender when it needs to.

I tried storing these mapped elements in an array, but the styling variables inside of each component are set to a single value when the component is stored. So rerendering the page doesn’t change the styling of these components even if the initial variables used to set their styles in state have changed.

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

 export default function App() {
  let [redText, setRedText] = useState(['This', 'That'])
  let [blueText, setBlueText] = useState(['The', 'Other'])
  let str = 'This That And The Other'
  let arr = str.split(" ")
  let componentsArr = null

  function firstRender() {
    componentsArr = []
    componentsArr.push(arr.map((el) => {
      return (
      <View style={styles.container}>
        <Text style={redText.includes(el) 
        ? styles.redText 
        : blueText.includes(el) 
        ? styles.blueText 
        : styles.blackText}>
          {el}
        </Text>
      </View>
    )
  }))
  return componentsArr
  }

  return (
    <View style={styles.container}>
      {componentsArr ? componentsArr : firstRender()}
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: "#fff",
    alignItems: "center",
    justifyContent: "center",
  },
  blackText: {
    color: "black"
  },
  redText: {
    color: "red"
  },
  blueText: {
    color: "blue"
  }
});

Let’s say I have some code like this that adds an onPress event to each element that will automatically change it to red. How can I do that without mapping through and creating the View and Text components from scratch?

When it is initially pushed into the array, all of the styling variables are set in stone. Is there some way to preserve the ternary operations?

2

Answers


  1. i’m not sure i understand well what you wanted to do but as i understood, every word in the text must manage it’s own toggle color ? So here how i would go.

    export const App = () => {
        const [texts, setTexts] = useState(['This', 'That', 'And', 'The', 'Other']);
    
        const renderTexts = () => {
            return texts.map(text => (
                <CustomTextColorToggle el={text} key={text} />
            ));
        };
    
      return (
        <View style={styles.container}>
          {renderTexts()}
        </View>
      );
    }
    
    // Here defaultColor is optional if you want to add some
    // more logic
    const CustomTextColorToggle = ({ defaultColor, el }) => {
        const [color, setColor] = useState(defaultColor);
        const styles = color === "red"
                    ? styles.redText 
            : color === "blue"
                    ? styles.blueText 
            : styles.blackText;
    
        return (
            <View style={styles.container}>
                <Text style={styles}>
                    {el}
                </Text>
            </View>
        );
    };
    

    Inside CustomTextColorToggle you can wrap the View with a Pressable to change the color using setColor

    Login or Signup to reply.
  2. This sounds like a good use case for memoization. If you want to prevent rendering of the list and its elements, unless styles change, you need to apply this in two places, the <View/> wrapper containing el and the entire list itself. Below is how I would apply it.

    import React, { useState, memo, useEffect } from "react";
    import { Text, View, StyleSheet } from "react-native";
    const styles = StyleSheet.create({
      container: {
        flex: 1,
        backgroundColor: "#fff",
        alignItems: "center",
        justifyContent: "center",
      },
      blackText: {
        color: "black",
      },
      redText: {
        color: "red",
      },
      blueText: {
        color: "blue",
      },
    });
    
    const stylesAreEqual = (prevProps, nextProps) => {
      //we only compare styles since we assume el prop wont change
      return (
        prevProps.redText === nextProps.redText &&
        prevProps.blueText === nextProps.blueText
      );
    };
    //this component only re-renders if redText or blueText change.
    //if el changes, it doesnt re-render. If you want this behavior to change,
    //remove the stylesAreEqual function from the memo callback
    const ViewItem = ({ el, redText, blueText }) => {
      //responds to any styles passed into the component
      const propStyle = redText.includes(el)
        ? styles.redText
        : blueText.includes(el)
        ? styles.blueText
        : styles.blackText;
      //if you want to control the styles indiviually for each view item without needing
      // to change redText, or blue Text props. It can managed locally here
      const [style, setStyle] = useState(propStyle);
      const onPressEvent = () => {
        //write any change to style on a press
        setStyle({ color: "black" });
      };
      useEffect(() => {
        //if you want to respond to changed passed from a higher up component.
        // though consider using useContext for this if your handling anything other
        // than primary types (i.e strings, boolean, etc.)
        setStyle(propStyle);
      }, [propStyle]);
      return (
        <View style={styles.container}>
          <Text onPress={onPressEvent} style={style}>
            {el}
          </Text>
        </View>
      );
    };
    const MemoViewItem = ({ el, redText, blueText }) =>
      memo(
        <ViewItem el={el} redText={redText} blueText={blueText} />,
        stylesAreEqual
      );
    const MemoList = ({ arr, redText, blueText }) =>
      //this means that unless the props passed into this component change,
      // it will not re-render, even if a component above it does for any case.
      memo(
        <>
          {arr.map((el) => {
            return <MemoViewItem el={el} redText={redText} blueText={blueText} />;
          })}
        </>
      );
    export default function App() {
      let [redText, setRedText] = useState(["This", "That"]);
      let [blueText, setBlueText] = useState(["The", "Other"]);
      let str = "This That And The Other";
      let arr = str.split(" ");
      let componentsArr = null;
    
      function firstRender() {
        componentsArr = [];
        componentsArr.push(
          <MemoList arr={arr} blueText={blueText} redText={redText} />
        );
        return componentsArr;
      }
    
      return (
        <View style={styles.container}>
          {componentsArr ? componentsArr : firstRender()}
        </View>
      );
    }
    

    Since I’m unsure if you want to change styles from onpress events, or from a general state coming from a higher component, I’ve included both in this example. In addition, depending on your use case, you can modify this above, and test where you need memoization or not since adding it does add extra overhead if its not necessary.

    As a note though, the only way to prevent the list from re-rendering at all (only once on mount), is to manage the styles in a local component

    In this case, removing redText and blueText from the App component, and removing the props on every component down the tree. From there, you can manage the styles inside the ViewItem component. Should this be the case, you can also remove the memo function. Below is an example.

    import React, { useState, memo } from "react";
    import { Text, View, StyleSheet } from "react-native";
    const styles = StyleSheet.create({
      container: {
        flex: 1,
        backgroundColor: "#fff",
        alignItems: "center",
        justifyContent: "center",
      },
      blackText: {
        color: "black",
      },
      redText: {
        color: "red",
      },
      blueText: {
        color: "blue",
      },
    });
    
    
    const ViewItem = ({ el }) => {
      //if you want to control the styles indiviually for each view item without needing
      // to change redText, or blue Text props. It can managed locally here
      const [redText, setRedText] = useState(["This", "That"]);
      const [blueText, setBlueText] = useState(["The", "Other"]);
      const style = redText.includes(el)
      ? styles.redText
      : blueText.includes(el)
      ? styles.blueText
      : styles.blackText;
      //const [styles, setStyles] = useState({});
      const onPressEvent = () => {
        //write any change to style on a press
        setRedText("Cool");
      };
    
      return (
        <View style={styles.container}>
          <Text onPress={onPressEvent} style={style}>
            {el}
          </Text>
        </View>
      );
    };
    
    //prevent list from re-rendering unless app prop changes
    const MemoList = ({ arr }) => memo(
      <>
        {arr.map((el) => (
          <ViewItem el={el} />
        ))}
      </>
    );
    
    export default function App() {
      let str = "This That And The Other";
      let arr = str.split(" ");
      let componentsArr = null;
      function firstRender() {
        componentsArr = [];
        componentsArr.push(<MemoList arr={arr} />);
        return componentsArr;
      }
    
      return (
        <View style={styles.container}>
          {componentsArr ? componentsArr : firstRender()}
        </View>
      );
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search