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>
);
};