Реагируйте на изменения собственного состояния без setState

#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])