Home > Enterprise >  Storing dynamically rendered page elements in state, and keeping variables functional?
Storing dynamically rendered page elements in state, and keeping variables functional?

Time:09-16

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?

CodePudding user response:

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

CodePudding user response:

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 } 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 MemoViewItem = ({ el, redText, blueText }) =>
  memo(
    <View style={styles.container}>
      <Text
        style={
          redText.includes(el)
            ? styles.redText
            : blueText.includes(el)
            ? styles.blueText
            : styles.blackText
        }
      >
        {el}
      </Text>
    </View>,
    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>
  );
}

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

  • Related