1

I have following tag selection:

enter image description here

It works fine but the problem is it hangs for a few seconds after I'm able to select another Tag. This is my code:

let selected = []
const TagScreen = () => {
  const [allInterests, setAllInterests] = useState(["interst1", "interst2", "interst3", "interst4", "interst5", "interst6", "interst7", "interst8", "interst9", "interst10", ...
  const [interests, setInterests] = useState([])

  const fire1 = (item) => {
    selected.unshift(item)
    setInterests([...interests, item])
  }
  const fire2 = (item) => {
    let index = selected.indexOf(item);
    setInterests([...interests, item])

    if(index!=-1){
      selected.splice(index, 1);
    }
  }
  return (
    <View>
      <View style={{display: "flex", flexWrap: "wrap", flexDirection: "row"}}>
        {allInterests.map((item) => {
          return(
            <View key={Math.random()}>
              {!selected.includes(item) ?
                <PressableBackground onPress={() => !selected.includes(item) ? fire1(item) : fire2(item)} >
                  <View style={{margin: 2.5,}}>
                    <Tag>
                      <Text style={{color: colors.text}}>{item}</Text>
                    </Tag>
                  </View>
                </PressableBackground>
              :
              <PressableBackground onPress={() => !selected.includes(item) ? fire1(item) : fire2(item)} >
                <View style={{margin: 2.5,}}>
                  <Tag color={colors.card}>
                    <Text style={{color: colors.background}}>{item}</Text>
                  </Tag>
                </View>
              </PressableBackground>
              }
            </View>
          )
        })}
      </View>
    </View>
  );
}

export default TagScreen;

I also tried React.Callback or React.useMemo. Sadly it didn't work

Henrik
  • 828
  • 8
  • 34
  • Try replace `Math.random()` with `item`. Changing key every time will negatively impact performance. Also please consider using `FlatList` – glinda93 Nov 16 '21 at 08:02

1 Answers1

2

It hangs because you are re-rendering every tag on the screen and it takes long time. useCallback and useMemo does nothing in your case because they only memoizes your function / function result, they won't stop your content from re-rendering without React.memo. You can seperate your tag component and its logic from TagScreen to save rendering.

You also need to replace selected array with useState to prevent it from getting unsynchronized.

Another problem is, you're using Math.random() as a key for your tag items. You should never use random strings as key. https://stackoverflow.com/a/29813867/5793132


const TagScreen = () => {
  const [allInterests, setAllInterests] = useState(["interst1", "interst2", "interst3", "interst4", "interst5", "interst6", "interst7", "interst8", "interst9", "interst10"]);
  // interests is unused?
  const [interests, setInterests] = useState([]);
  const [selected, setSelected] = useState([]);

  // Added useCallback to memoize function because this is going to be passed to a memoized component.
  // Replaced direct state changes with state action to remove interests and selected dependencies
  const fire1 = React.useCallback((item) => {
    setSelected((prev) => [item, ...prev]);
    setInterests((prev) => [...prev, item]);
  }, []);

  // Added useCallback to memoize function because this is going to be passed to a memoized component.
  // Replaced direct state changes with state action to remove interests and selected dependencies
  const fire2 = React.useCallback((item) => {
    setInterests((prev) => [...prev, item]);

    setSelected((prev) => {
      let index = prev.indexOf(item);

      if (index !== -1) {
        prev.splice(index, 1);
      }

      return [...prev];
    });
  }, []);

  return (
    <View>
      <View style={{display: "flex", flexWrap: "wrap", flexDirection: "row"}}>
        {allInterests.map((item) => {
          return (
            // Math.random() replaced with item
            // Created isSelected prop because you should only change props when really necessary while using React.memo
            // because React.memo will re-render your component when a prop changes.
            // passing other required props.
            <TagItem key={item} item={item} isSelected={selected.includes(item)} fire1={fire1} fire2={fire2} />
          )
        })}
      </View>
    </View>
  );
};

// TagItem seperated from TagScreen component.
const TagItem = React.memo(({item, isSelected, fire1, fire2}) => {
  return (
    <View>
      {isSelected ? (
        <PressableBackground onPress={() => fire2(item)} >
          <View style={{margin: 2.5,}}>
            <Tag>
              <Text style={{color: colors.text}}>{item}</Text>
            </Tag>
          </View>
        </PressableBackground>
      ) : (
        <PressableBackground onPress={() => fire1(item)} >
          <View style={{margin: 2.5,}}>
            <Tag color={colors.card}>
              <Text style={{color: colors.background}}>{item}</Text>
            </Tag>
          </View>
        </PressableBackground>
      )}
    </View>
  )
});

export default TagScreen;
Ugur Eren
  • 1,968
  • 2
  • 11
  • 21
  • *Note: I didn't tested it but there should be not problem with this code, let me know if this will still re-render or you get an unexpected error.* – Ugur Eren Nov 15 '21 at 20:13
  • sadly I get "undefined is not a function" (const fire1 = React.useCallback((item) => { setSelected((prev) => prev.unshift(item));......) – Henrik Nov 15 '21 at 21:34
  • And I get this: Warning: Cannot update a component (`TagScreen`) while rendering a different component (`_c`). To locate the bad setState() call inside `_c`, follow the stack trace as described in https://reactjs.org/link/setstate-in-render – Henrik Nov 15 '21 at 21:44
  • after improving some points it works and it's faster but not that fast I want it to be. At first thank you very much. But do you know any other way? :) – Henrik Nov 15 '21 at 22:15
  • @Henrik I've edited my answer, can you try it now? – Ugur Eren Nov 16 '21 at 07:58
  • 1
    Thank you so much. It works perfectly and is super fast – Henrik Nov 16 '21 at 14:17