Home > Back-end >  how can conditional rendering reflect the state from a list of Boolean using hook?
how can conditional rendering reflect the state from a list of Boolean using hook?

Time:11-01

The Goal:

My React Native App shows a list of <Button /> based on the value from a list of Object someData. Once a user press a <Button />, the App should shows the the text that is associated with this <Button />. I am trying to achieve this using conditional rendering.

The Action:

So first, I use useEffect to load a list of Boolean to showItems. showItems and someData will have the same index so I can easily indicate whether a particular text associated with <Button /> should be displayed on the App using the index.

The Error:

The conditional rendering does not reflect the latest state of showItems.

The Code:

Here is my code example

import {someData} from '../data/data.js';

const App = () => {
  const [showItems, setShowItems] = useState([]);

  useEffect(() => {
    const arr = [];
    someData.map(obj => {
      arr.push(false);
    });
    setShowItems(arr);
  }, []);

  const handlePressed = index => {
    showItems[index] = true;
    setShowItems(showItems);
    //The list is changed. 
    //but the conditional rendering does not show the latest state
    console.log(showItems);
  };

  return (
    <View>
      {someData.map((obj, index) => {
        return (
          <>
            <Button
              title={obj.title}
              onPress={() => {
                handlePressed(index);
              }}
            />
            {showItems[index] && <Text>{obj.item}</Text>}
          </>
        );
      })}
    </View>
  );
};


CodePudding user response:

showItems[index] = true;
setShowItems(showItems);

React is designed with the assumption that state is immutable. When you call setShowItems, react does a === between the old state and the new, and sees that they are the same array. Therefore, it concludes that nothing has changed, and it does not rerender.

Instead of mutating the existing array, you need to make a new array:

const handlePressed = index => {
  setShowItems(prev => {
    const newState = [...prev];
    newState[index] = true;
    return newState;
  });
}

CodePudding user response:

This is because react is not identifying that your array has changed. Basically react will assign a reference to the array when you define it. But although you are changing the values inside the array, this reference won't be changed. Because of that component won't be re rendered.

And furthermore, you have to pass the key prop to the mapped button to get the best out of react, without re-rendering the whole button list. I just used trimmed string of your obj.title as the key. If you have any sort of unique id, you can use that in there.

So you have to notify react, that the array has updated.

import { someData } from "../data/data.js";

const App = () => {
  const [showItems, setShowItems] = useState([]);

  useEffect(() => {
    const arr = [];
    someData.map((obj) => {
      arr.push(false);
    });
    setShowItems(arr);
  }, []);

  const handlePressed = (index) => {
    setShowItems((prevState) => {
      prevState[index] = true;
      return [...prevState];
    });
  };

  return (
    <View>
      {someData.map((obj, index) => {
        return (
          <>
            <Button
              key={obj.title.trim()}
              title={obj.title}
              onPress={() => {
                handlePressed(index);
              }}
            />
            {showItems[index] && <Text>{obj.item}</Text>}
          </>
        );
      })}
    </View>
  );
};
  • Related