#reactjs #react-native #react-hooks
#reactjs #реагировать-родной #реагирует на перехваты
Вопрос:
У меня есть экран, представляющий расписание, которое позволяет пользователю присваивать каждой неделе название. Как только пользователь закончит редактирование всех недель, он нажмет на галочку, и приложение должно обновить серверную часть.
Я получаю очень странную ошибку, когда при первом нажатии на флажок редактировать данные обновляются (в журнале указано «editData изменено» и изменяется пользовательский интерфейс), но когда я печатаю состояние внутри updateTheDB, оно не было обновлено. Если я попытаюсь снова войти в режим редактирования и сохранить без внесения каких-либо новых изменений, предыдущие изменения будут обновлены внутри updateTheDB.
Я думал, что это копия по ссылке, а не проблема значения, но я использую JSON.parse(JSON.stringify для копирования, так что этого не может быть.
Вызов setState (setEditData) находится внутри onSavePressed, который находится в модальном режиме, который открывается, когда пользователь пытается назвать неделю.
Кто-нибудь знает, что могло быть причиной этого?
Редактировать
Я хочу, чтобы вызвать рендеринг при вызове setEditToServer
Это мой код:
const Schedule = (props: IPorps) => {
const { week_names_props, navigation } = props;
const days = ['s', 'm', 't', 'w', 't', 's', 'w'];
//bottom Modal
const [active_modal, setActiveModal] = useState<MProps>(null)
//data
const [serverData, setServerData] = useState<IData>({ weekNames: week_names_props, weekEvents: {} })
//edit_mode data
const [editData, setEditData] = useState<IData>(null)
//other
const [isLoading, setIsLoading] = useState<boolean>(false)
const [editable, setEditable] = useState<boolean>(false)
const doTheLoadingThingy = (): void => {
Axios.get(`api/weeks/getWeekNameByCompanyId`, {
params: {
company_id: 1,
},
}).then((response) => {
setServerData({ ...serverData, weekNames: response.data })
//useEffect will hide the loading and the modal
})
.catch(() => { setIsLoading(false); errorToast("get") })
}
const setEditToServer = () => {
console.log("setEditToServer"
setEditData({
weekNames: JSON.parse(JSON.stringify(serverData.weekNames)),
weekEvents: {}
})
}}
useEffect(() => {
setEditToServer()
setIsLoading(false)
}, [serverData])
useEffect(() => {
console.log("editData changed")
}, [editData])
const updateTheDB = () => {
setIsLoading(true)
console.log(editData)
//send to backend
}
useEffect(() => {
navigation.setOptions({
headerLeft: () => (
<View style={{ flexDirection: "row", justifyContent: "space-around", alignItems: "center", paddingHorizontal: 30 }}>
{editable ? (
<>
<Icon
name={"check"}
onPress={() => {
updateTheDB()
setEditable(false)
}}/>
<Icon
name={"cancel"}
onPress={() => {
console.log("cancel")
setEditToServer()
setEditable(false)
}}/>
</>
) : (
<Icon
name={'edit'}
onPress={() => setEditable(true)}/>
)}
</View>
)
})
}, [editable])
return (
<>
{isLoading ?
(<ActivityIndicator size="large" />) :
(
<>
<BottomModal
innerComponent={active_modal ? active_modal.modal : null}
innerComponentProps={active_modal ? active_modal.props : null}
modalStyle={active_modal ? active_modal.style : null}
modalVisible={(active_modal != null)}
onClose={() => {
setActiveModal(null);
}}
/>
<WeekDaysHeader />
{editData ?
(<FlatList
data={editData.weekNames}
keyExtractor={(item) => item.week.toString()}
style={styles.flatListStyle}
// extraData={editData?editData.weekEvents:null}
renderItem={({ item }) => {
return (
<Week
days={days}
events={editData.weekEvents[item.week.toString()]}
dayComponents={ScheduleScreenDay}
week_name={item.week_name ? item.week_name : null}
week_number={Number(item.week)}
onHeaderPressed={editable ?
(week_number, week_title) => {
console.log("Pressed", week_number, week_title)
setActiveModal({
props: {
week_number_props: week_number,
week_title_props: week_title,
onSavePressedProps: (new_name) => {
if (new_name) {
let tmp = JSON.parse(JSON.stringify(editData.weekNames))
const i = tmp.findIndex((item) => item.week.toString() === week_number.toString())
tmp[i].week_name = new_name
setEditData((prev) => ({ weekNames: tmp, weekEvents: prev.weekEvents }))
}
}
}, modal: NameWeekModalComponet,
style: styles.weekNameModalStyle
});
} : undefined}
/>
);
}}
/>) : null}
</>
)
}
</>)
};
export default Schedule;
Ответ №1:
Я думал, что это копия по ссылке, а не проблема со значением, но я использую JSON.parse(JSON.stringify) для копирования, так что этого не может быть.
Это именно проблема:
// Always true
JSON.parse(JSON.stringify(['a'])) !== JSON.parse(JSON.stringify(['a']))
Поэтому при setEditToServer
каждом вызове компонент будет повторно отображаться, это потому, что React проводит поверхностное сравнение с предыдущим состоянием при принятии решения о рендеринге.
const setEditToServer = () => {
// Always re-render
setEditData({
weekNames: JSON.parse(JSON.stringify(serverData.weekNames)),
weekEvents: {}
})
}}
Комментарии:
1. Спасибо за быстрый ответ. Я не вызываю setEditToServer после первоначального рендеринга, так что это не должно быть проблемой, и если я буду вызывать его в будущем, я хочу, чтобы он вызывал рендеринг. Проблема в том, что значение в пользовательском интерфейсе изменяется, но когда я пытаюсь посмотреть на состояние, которое им управляет (оно же editData), не изменилось
Ответ №2:
Проблема была в этом эффекте использования:
useEffect(() => {
navigation.setOptions({
headerLeft: () => (
//...
)
})
}, [editable])
Функции внутри него использовали editData
состояние, но у useEffect не было его в списке deps, поэтому при нажатии onPress он получил старое состояние editData
. Решение состояло в том, чтобы добавить editData
его в список deps. Вот так:
useEffect(() => {
navigation.setOptions({
headerLeft: () => (
//...
)
})
}, [editable, editData])