Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
108 views
in Technique[技术] by (71.8m points)

reactjs - useState not behaving as expected

I have a problem with useState. editorDataOpen is updating correctly when set from openEditorData , but not when set from closeEditorData. The line console.log('Entering Profile > closeEditorData()') is reached without a problem.

The output I see in the console log is:

  • Render
  • Render
  • false (editorDataOpen set for the first time)
  • Render
  • Render
  • Render
  • Render
  • true (editorDataOpen set to true by a click, which opens Editor)
  • Entering Profile > closeEditorData() (triggered by a different click to close Editor, should set editorDataOpen to false, but doesn't)
  • Render
  • Render

and that's it, it never prints a last false, which would mean editorDataOpen is never set?

I've spent too much time on this today and I just cannot see where the bug is. Can someone help please? This is the code in question:

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

EDIT: This one has been solved by Dehan de Croos, many thanks!


So as Dehan mentions below, the event was bubbling up and triggering openEditorData in ContainerGrid. The whole thing was mistifying because editorImageOpen was working correctly while editorDataOpen was not, and they both do the same thing: open an Editor window. Once Dehan solved the mistery, I realized that the differene between the 2 is that inside ImageCapsule there is a ClickLayer component, which is there just to catch the click and the callback. I did not use a ClickLayer with ContainerGrid, and that is why the event was able to bubble up. Following Dehan's advice, I solved just by adding a ClickLayer inside ContainerGrid, like this:

            <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>
question from:https://stackoverflow.com/questions/65895239/usestate-not-behaving-as-expected

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

It would be better to have a working code snippet to diagnose this, but there is a good chance that the event might be bubbling up the component tree and triggering the openEditorData event here.

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

To check this quickly move the component shown below outside the <ContainerGrid ... /> component. And check whether this fixes things.

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}
                />
            }

If now it's working properly and you desperately need to maintain the above component hierarchy/structure. You can call stopPropegation but you will need to have the native JS event with you. To explain how to do this I need to know what <EditorProfileData ... /> looks like. But assuming the close prop does return the native click event as a callback, the fix will look something like this.

            {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}
                />
            }

If not we need to find the original onClick event and pass up to that prop callback.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...