I'm working with my React Native project where I have like a "form" in which the user selects what day and between what hours they're available. There are two components involved:
This is the DaySelector:
import * as React from "react";
import { View, Text, StyleSheet, Button, Alert } from "react-native";
import { TouchableOpacity } from "react-native-gesture-handler";
import colors from "../assets/constants/style-variables";
interface Item {
selected: Boolean;
day: String;
}
function Day(props) {
return (
<TouchableOpacity
onPress={props.onPress}
style={props.selected ? styles.buttonActive : styles.button}
>
<Text>{props.day}</Text>
</TouchableOpacity>
);
}
export default class DaySelector extends React.Component<
{ onChange },
{ selectedItems: undefined | Item[]; items: Item[] }
> {
constructor(props) {
super(props);
this.state = {
selectedItems: [],
items: [
{
selected: false,
day: "Lu",
},
{
selected: false,
day: "Ma",
},
{
selected: false,
day: "Mi",
},
{
selected: false,
day: "Ju",
},
{
selected: false,
day: "Vi",
},
{
selected: false,
day: "Sa",
},
{
selected: false,
day: "Do",
},
],
};
}
onPress = (index) => {
let items = [...this.state.items];
let item = { ...items[index] };
item.selected = !item.selected;
items[index] = item;
this.setState({ items });
this.setState((prevState) => ({
selectedItems: prevState.items.filter((i) => i.selected),
}));
this.props.onChange(this.state.selectedItems);
};
render() {
return (
<View>
<View style={styles.container}>
{this.state.items.map((item, index) => {
return (
<Day
key={index}
selected={item.selected}
day={item.day}
onPress={this.onPress.bind(this, index)}
/>
);
})}
</View>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flexDirection: "row",
},
button: {
margin: 5,
borderColor: colors.lightGray,
borderWidth: 2,
borderRadius: 40,
padding: 7,
},
buttonActive: {
margin: 5,
borderColor: colors.primary,
borderWidth: 2,
backgroundColor: colors.primary,
borderRadius: 50,
padding: 7,
},
});
As you can see, it collects all the days selected and passes them into the onChange
prop. That way I can access the results in my parent component, which is this one:
import React, { useState, useCallback, useReducer } from "react";
import { useMutation } from "@apollo/react-hooks";
import { REGISTER } from "../Queries";
import Loader from "../components/Loader";
import DateTimePicker from "@react-native-community/datetimepicker";
import { View, StyleSheet } from "react-native";
import colors from "../assets/constants/style-variables";
import { TouchableOpacity } from "react-native-gesture-handler";
import PrimaryText from "../assets/constants/PrimaryText";
import DaySelector from "../components/DaySelector";
function reducer(state, action) {
switch (action.type) {
case "refresh-days":
return {
dayList: action.value,
};
default:
return state;
}
}
export default function ScheduleSelect({ navigation, route }) {
const [loadingRegistration, setLoadingRegistration] = useState(null);
const [showTimePicker1, setShowTimePicker1] = useState(false);
const [showTimePicker2, setShowTimePicker2] = useState(false);
const [time1, setTime1] = useState(null);
const [time2, setTime2] = useState(null);
const [{ dayList }, dispatch] = useReducer(reducer, { dayList: [] });
const show1 = () => {
setShowTimePicker1(true);
};
const show2 = () => {
setShowTimePicker2(true);
};
const handleTimePick1 = (_event, selectedTime) => {
setShowTimePicker1(false);
if (selectedTime !== undefined) {
setTime1(selectedTime);
}
};
const handleTimePick2 = (_event, selectedTime) => {
setShowTimePicker2(false);
if (selectedTime !== undefined) {
setTime2(selectedTime);
}
};
return (
<View>
<View style={styles.container}>
<PrimaryText fontSize={20} margin={22} textAlign={"center"}>
Recibirás turnos en estos horarios:
</PrimaryText>
<View style={styles.cardHolder}>
<View style={styles.card}>
<View style={styles.timeHelper}>
<PrimaryText textAlign={"center"}>Desde</PrimaryText>
</View>
{time1 && (
<TouchableOpacity onPress={show1}>
<View style={styles.timeSelected}>
<PrimaryText textAlign={"center"} fontSize={36}>
{`${time1?.getHours()}:${
time1.getMinutes() < 10
? "0" + time1.getMinutes()
: time1.getMinutes()
}`}
</PrimaryText>
</View>
</TouchableOpacity>
)}
{!time1 && (
<TouchableOpacity style={styles.clockEdit} onPress={show1}>
<PrimaryText
fontSize={36}
letterSpacing={2}
textAlign={"center"}
>
--:--
</PrimaryText>
</TouchableOpacity>
)}
{showTimePicker1 && (
<DateTimePicker
value={new Date()}
testID="dateTimePicker"
mode={"time"}
is24Hour={true}
display="default"
onChange={handleTimePick1}
/>
)}
</View>
<PrimaryText fontSize={36} margin={7}>
-
</PrimaryText>
<View style={styles.card}>
<View style={styles.timeHelper}>
<PrimaryText textAlign={"center"}>Hasta</PrimaryText>
</View>
{time2 && (
<TouchableOpacity onPress={show2}>
<View style={styles.timeSelected}>
<PrimaryText textAlign={"center"} fontSize={36}>
{`${time2?.getHours()}:${
time2.getMinutes() < 10
? "0" + time2.getMinutes()
: time2.getMinutes()
}`}
</PrimaryText>
</View>
</TouchableOpacity>
)}
{!time2 && (
<TouchableOpacity style={styles.clockEdit} onPress={show2}>
<PrimaryText
fontSize={36}
letterSpacing={2}
textAlign={"center"}
>
--:--
</PrimaryText>
</TouchableOpacity>
)}
{showTimePicker2 && (
<DateTimePicker
value={new Date()}
testID="dateTimePicker"
mode={"time"}
is24Hour={true}
display="default"
onChange={handleTimePick2}
/>
)}
</View>
</View>
{time2?.getTime() < time1?.getTime() && (
<PrimaryText color={colors.warning}>
Por favor, seleccioná horarios válidos
</PrimaryText>
)}
<PrimaryText margin={5}> Los días: </PrimaryText>
<View style={styles.card}>
<DaySelector
onChange={(value) => dispatch({ type: "refresh-days", value })}
/>
</View>
</View>
<View style={styles.buttonWrapper}>
<TouchableOpacity
style={styles.button}
onPress={() => {
console.log(dayList);
}}
>
<PrimaryText color={colors.iceWhite}>SIGUIENTE</PrimaryText>
</TouchableOpacity>
</View>
</View>
);
}
const styles = StyleSheet.create({
container: {
height: "88%",
justifyContent: "center",
paddingTop: 15,
alignItems: "center",
},
clockEdit: {
padding: 7,
},
button: {
alignSelf: "flex-end",
backgroundColor: colors.primary,
padding: 7,
margin: 15,
borderRadius: 15,
},
buttonWrapper: {
justifyContent: "flex-end",
alignItems: "flex-end",
paddingRight: 15,
},
card: {
flexDirection: "row",
justifyContent: "center",
minWidth: "35%",
backgroundColor: colors.iceWhite,
borderRadius: 10,
padding: 20,
marginBottom: 10,
shadowColor: "#000",
shadowOffset: {
width: 0,
height: 2,
},
shadowOpacity: 0.25,
shadowRadius: 3.84,
elevation: 5,
},
cardHolder: {
flexDirection: "row",
alignItems: "center",
},
timeHelper: {
justifyContent: "center",
position: "absolute",
marginTop: 5,
},
timeSelected: {
alignItems: "center",
justifyContent: "center",
},
editIcon: {
right: 3,
top: 3,
position: "absolute",
},
});
The problem is that when I console.log
the value of the onChange
prop, I get the items out of sync. To try and solve this, you see that I used a reducer, since the task is dependent of the result of the action. However, it does not change anything and still returns "outdated" information.
How can I sync both of the components?
This problems might come from your setState in your first component. you are calling the setState and then use the state straight after. But setState is asynchronous. So when you call your onChange, the state is still the previous one.
The prototype of setState is
setState(callback: (currentState) => newState | newState, afterSetState: (newState) => void)
So you can use the afterSetState callback to receive the new state and call your props.onChange
Also you are calling setState twice, but you could call it once and manipulate the state to return all in one.
However, if I could give you an advice, it'd be better controlling the state from the parent instead of the child. It avoids you calling a setState in the child and call the onChange in the parent which will set the state somewhere else again.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.