useState 未按预期运行

useState not behaving as expected

我对 useState 有疑问。 editorDataOpen 在从 openEditorData 设置时正确更新,但在从 closeEditorData 设置时不更新。 console.log('Entering Profile > closeEditorData()') 行没有问题。

我在控制台日志中看到的输出是:

就是这样,它从不打印最后一个 false,这意味着 editorDataOpen 从未设置?

我今天在这上面花了太多时间,我就是看不出错误在哪里。有人可以帮忙吗?这是有问题的代码:

import React from 'react'
import {withTheme} from 'styled-components'
import {withContext} from '../../context/ContextHOC'
import withLoadingScreen from '../hocs/LoadingScreenHOC'
import {getEditorWidth} from '../../functions/funcEditor'
import {saveImagePlus} from '../../functions/funcDataSaver'
import Submodule from '../ui/Submodule'
import ImageCapsule from '../ui/ImageCapsule'
import EditorImage from '../editors/EditorImage'
import EditorProfileData from '../editors/EditorProfileData'
import Spacer from '../ui/Spacer'
import Table from '../ui/Table'
import ContainerGrid from '../ui/ContainerGrid'
import * as ops from '../../functions/funcStringMath'
import * as parse from '../../functions/funcDataParser'

const Profile = (props) => {

    const s = props.theme.sizes
    const c = props.theme.cards

    const {setLoadingOn, setLoadingOff} = props
    const [image, setImage] = React.useState(props.context.current.modules.me.profile.image)
    const [editorImageOpen, setEditorImageOpen] = React.useState(false)
    const [editorDataOpen, setEditorDataOpen] = React.useState(false)
    
    const openEditorImage = () => setEditorImageOpen(true)
    const openEditorData = () => setEditorDataOpen(true)
    
    const closeEditorImage = () => {
        setEditorImageOpen(false)
        setLoadingOff()
    }

    const closeEditorData = () => {
        console.log('Entering Profile > closeEditorData()')
        setEditorDataOpen(false)
        setLoadingOff()
    }

    React.useEffect(() => console.log(editorDataOpen), [editorDataOpen])

    const updateAfterSavingImage = (img) => {

        setImage({
            url: img.url,
            scale: img.scale,
            position: img.position
        })

        closeEditorImage()

    }

    const handleImageChanged = (img) => {
        
        if (img != undefined){

            setLoadingOn()

            const data = {
                companyId: props.context.current.company.id,
                userId: props.context.current.user.id,
                routeFile: props.context.routes.meProfileImage,
                routeData: props.context.routes.meProfileImageData,
            }
        
            saveImagePlus(img, data, updateAfterSavingImage)

        }
        else {
            console.log('Error: Image received is undefined, cannot save.')
            closeEditorImage()
        }

    }

    const spacer =
        <Spacer
            width = '100%'
            height = {s.spacing.default}
        />

    const unparsedData = props.context.current.modules.me.profile.data
    const parsedData = parse.profileData(props.context.current.modules.me.profile.data)

    console.log('Render')

    return(

        <Submodule
            isMobile = {c.cardsPerRow == 1 ? true : false}
            header = {{
                text: 'Profile',
            }}
            {...props}
        >

            <ImageCapsule 
                onClick = {openEditorImage}
                id = {'container_imageprofile'}
                width = '100%'
                image = {image}
                $nodrag
            />

            {editorImageOpen &&
                <EditorImage
                    open = {editorImageOpen}
                    closeSaving = {handleImageChanged}
                    closeWithoutSaving = {closeEditorImage}
                    image = {image}
                    width = {getEditorWidth(1, c.cardsPerRow, c.card.width, s.spacing.default)}
                    header = {{
                        text: 'Edit Profile Image',
                    }}
                />
            }

            {spacer}
            {spacer}
            
            <ContainerGrid
                // bgcolor = '#C43939'
                width = '100%'
                justify = {s.justify.center}
                onClick = {openEditorData}
                $clickable
            >
            
                <Table
                    $nomouse
                    width = '100%' 
                    data = {parsedData}
                    settings = {{

                        cell: {
                            padleft: s.spacing.default,
                            padright: s.spacing.default,
                            padtop: ops.round((ops.divide([s.spacing.default, 4]))),
                            padbottom: ops.round((ops.divide([s.spacing.default, 4]))),
                        },
                        
                        columns: [
                            {type: 'defaultRight', width: '30%'},
                            {type: 'default', width: '70%'},
                        ]

                    }}
                />

                {editorDataOpen &&
                    <EditorProfileData
                        open = {editorDataOpen}
                        close = {closeEditorData}
                        width = {getEditorWidth(1, c.cardsPerRow, c.card.width, s.spacing.default)}
                        header = {{
                            text: 'Edit Profile Data',
                        }}
                        data = {unparsedData}
                    />
                }

            </ContainerGrid>

            {spacer}
            {spacer}

        </Submodule>

    )
}

export default (
    withTheme(
        withContext(
            withLoadingScreen(
                Profile
            )
        )
    )
)

编辑:Dehan de Croos 已经解决了这个问题,非常感谢!


因此,正如 Dehan 在下面提到的,该事件冒泡并触发了 ContainerGrid 中的 openEditorData。整个事情令人困惑,因为 editorImageOpen 工作正常而 editorDataOpen 不工作,并且它们都做同样的事情:打开一个编辑器 window。 当 Dehan 解开谜团后,我意识到两者之间的区别在于 ImageCapsule 内部有一个 ClickLayer 组件,它只是用来捕获点击和回调。 我没有将 ClickLayer 与 ContainerGrid 一起使用,这就是事件能够冒泡的原因。 按照 Dehan 的建议,我通过在 ContainerGrid 中添加一个 ClickLayer 来解决问题,如下所示:

            <ContainerGrid
                // bgcolor = '#C43939'
                width = '100%'
                justify = {s.justify.center}
                // onClick = {openEditorData}
                $clickable
            >

                <ClickLayer
                    onClick = {openEditorData}    
                />
            
                <Table
                    $nomouse
                    width = '100%' 
                    data = {parsedData}
                    settings = {{

                        cell: {
                            padleft: s.spacing.default,
                            padright: s.spacing.default,
                            padtop: ops.round((ops.divide([s.spacing.default, 4]))),
                            padbottom: ops.round((ops.divide([s.spacing.default, 4]))),
                        },
                        
                        columns: [
                            {type: 'defaultRight', width: '30%'},
                            {type: 'default', width: '70%'},
                        ]

                    }}
                />

                {editorDataOpen &&
                    <EditorProfileData
                        open = {editorDataOpen}
                        close = {closeEditorData}
                        width = {getEditorWidth(1, c.cardsPerRow, c.card.width, s.spacing.default)}
                        header = {{
                            text: 'Edit Profile Data',
                        }}
                        data = {unparsedData}
                    />
                }

            </ContainerGrid>

最好有一个有效的代码片段来诊断此问题,但事件很有可能会冒泡到组件树并在此处触发 openEditorData 事件。

              <ContainerGrid
                // bgcolor = '#C43939'
                width = '100%'
                justify = {s.justify.center}
                onClick = {openEditorData}
                $clickable
             >

要快速检查,请将下面显示的组件移到 <ContainerGrid ... /> 组件之外。并检查这是否可以解决问题。

To clarify here, The move of code should happen so that < EditorProfileData ... /> will never appear as a child component of <ContainerGrid ... />

            {editorDataOpen &&
                <EditorProfileData
                    open = {editorDataOpen}
                    close = {closeEditorData}
                    width = {getEditorWidth(1, c.cardsPerRow, c.card.width, s.spacing.default)}
                    header = {{
                        text: 'Edit Profile Data',
                    }}
                    data = {unparsedData}
                />
            }

如果现在它工作正常并且您迫切需要维护上述组件hierarchy/structure。你可以打电话 stopPropegation 但您需要随身携带原生 JS 事件。为了解释如何做到这一点,我需要知道 <EditorProfileData ... /> 是什么样子的。但是假设 close 道具将 return 原生点击事件作为回调,修复看起来像这样。

            {editorDataOpen &&
                <EditorProfileData
                    open = {editorDataOpen}
                    //close = {closeEditorData}

                    // If close provides the native event use following
                    close = { e => {
                        e.stopPropagation();
                        closeEditorData();
                    }}

                    width = {getEditorWidth(1, c.cardsPerRow, c.card.width, s.spacing.default)}
                    header = {{
                        text: 'Edit Profile Data',
                    }}
                    data = {unparsedData}
                />
            }

如果不是,我们需要找到原始的 onClick 事件并将其传递给该道具回调。