I have a functional component ViewAlarms that stores the alarm in an objects array using use state, and another functional component AlarmCard that is responsible for rendering the cards that hold the main info of an alarm.
the ViewAlarms Component handles selection to enter "editing" mode where the user clicks the card to select it for deletion. However, changing the alarm.selected attribute doesn’t seem to update the alarm card unless one of the AlarmCard’s children is updated e.g. toggle the switch then the AlarmCard shows that it has been selected.
ViewAlarms.jsx
function AlarmsViewScreen({ navigation }) {
const { theme, setTheme } = useTheme();
styles = {
primaryColor: theme,
};
const [alarms, setAlarms] = useState([]);
const addAlarm = (newAlarm) => {
setAlarms([...alarms, newAlarm]);
};
const selectAlarm = (alarm) => {
alarm.selected = true;
setMode("editing");
setAlarms[alarms];
};
const [mode, setMode] = useState("viewing");
const onTapAlarm = (alarm) => {
if (mode == "editing") {
selectAlarm(alarm);
} else {
}
};
return (
<View style={{ flex: 1, backgroundColor: "#242424" }}>
<StatusBar translucent={true} />
<Text
style={{
fontSize: 32,
paddingTop: 50,
alignSelf: "center",
color: "#F8E5EE",
fontFamily: "bold",
}}
>
Alarms
</Text>
<Image
source={require("../assets/noalarms.png")}
style={
alarms.length == 0
? { alignSelf: "center", marginTop: 150 }
: { display: "none" }
}
/>
<ScrollView style={{ flex: 1 }}>
{alarms.map((alarm, index) => (
<TouchableOpacity
activeOpacity={0.8}
onLongPress={() => {
selectAlarm(alarm);
}}
onPress={() => {
onTapAlarm(alarm);
}}
>
<AlarmCard mode={mode} alarm={alarm} />
</TouchableOpacity>
))}
</ScrollView>
<TouchableOpacity
style={{
position: "absolute",
bottom: 0,
alignSelf: "center",
margin: 20,
}}
>
<Pressable
onPress={() => {
addAlarm({
selected: false,
alarmName: "Fajr",
alarmTime: "5:40",
M: "AM",
On: true,
Days: ["s1", "m"],
});
}}
>
<View
style={{ flex: 1, justifyContent: "center", alignItems: "center" }}
>
<Image
source={require("../assets/newalarm.png")}
style={{ tintColor: styles.primaryColor, resizeMode: "contain" }}
/>
<Image
source={require("../assets/plus.png")}
style={{ position: "absolute" }}
/>
</View>
</Pressable>
</TouchableOpacity>
</View>
);
}
AlarmCard.jsx
export const AlarmCard = ({ mode, alarm }) => {
console.log(alarm);
const images = {
selected: require("../assets/selected.png"),
notselected: require("../assets/notselected.png"),
};
const { theme, setTheme } = useTheme();
const [days, setDays] = useState([
{ fri: { display: "F", on: false } },
{ sat: { display: "S", on: true } },
{ sun: { display: "S", on: false } },
{ mon: { display: "M", on: false } },
{ tue: { display: "T", on: false } },
{ wed: { display: "W", on: true } },
{ thu: { display: "T", on: false } },
]);
const index_to_day = ["fri", "sat", "sun", "mon", "tue", "wed", "thu"];
const toggleDay = (index) => {
const updated = [...days];
updated[index][index_to_day[index]].on = updated[index][index_to_day[index]]
.on
? false
: true;
setDays(updated);
};
const [on, setOn] = useState(true);
const toggleSwitch = () => {
const off = on ? false : true;
setOn(off);
};
return (
<View>
<View
style={{
backgroundColor: "#535353",
width: "90%",
height: 240,
margin: "5%",
borderRadius: 20,
}}
>
<Text
style={{
fontSize: 28,
fontFamily: "bold",
color: theme,
margin: 20,
marginBottom: 10,
}}
>
{alarm.alarmName}
</Text>
<View style={{ flexDirection: "row" }}>
<Text
style={{
fontSize: 100,
fontFamily: "bold",
color: "#F8E5EE",
marginHorizontal: 15,
}}
>
{alarm.alarmTime}
</Text>
<Switch
trackColor={{ false: "#838383", true: theme }}
thumbColor={"#F8E5EE"}
onValueChange={toggleSwitch}
value={on}
style={{
marginLeft: 80,
transform: [{ scaleX: 2 }, { scaleY: 2 }],
}}
/>
</View>
<View
style={
mode == "editing"
? {
position: "absolute",
right: 0,
marginRight: 20,
marginTop: 20,
}
: { display: "none" }
}
>
<Image
source={alarm.selected ? images["selected"] : images["notselected"]}
tintColor={alarm.selected ? theme : "#F8E5EE"}
/>
</View>
<View
style={{
backgroundColor: "#838383",
width: "100%",
height: "25%",
borderBottomLeftRadius: 20,
borderBottomRightRadius: 20,
position: "absolute",
bottom: 0,
}}
>
<View style={{ flex: 1, flexDirection: "row", marginHorizontal: 10 }}>
{days.map((day, index) => (
<Pressable
onPress={() => {
toggleDay(index);
}}
key={index}
style={{
marginHorizontal: 5,
marginTop: 13,
borderRadius: 5,
borderColor: theme,
borderWidth: 2,
width: 32,
height: 32,
backgroundColor: day[index_to_day[index]].on
? theme
: "transparent",
alignItems: "center",
}}
>
<Text
style={{ fontFamily: "bold", fontSize: 24, color: "#F8E5EE" }}
>
{day[index_to_day[index]].display}
</Text>
</Pressable>
))}
</View>
</View>
</View>
</View>
);
};
i tried using useEffect but it didn’t work
and the console.log(alarm) ** doesn’t get called unless some child of the AlarmCard is updated e.g. toggle switch
2
Answers
If you want to set a complex state value, use a setter with the callback-function:
React state updates need to be done with a new array or object reference to trigger a re-render.
Try modifying the selectAlarm function to trigger a rerender when an alarm is selected.