Why isn't functional child component changing state on props change?

Viewed 38

I have a form template with a popup as child component. The popup works to display response from server for a form query(ajax) to login/forgot-password, etc. and will disappear automatically after some time.

Now, for the first time popup works fine and displays response and disappears but if I try it again from same page (as query is sent as ajax and page reloading doesn't happen) the message gets updated but it won't appear.

And what I have concluded to be the problem is that state of child component(isShown) not updating on message update and I can't seem to solve this. Or it can be I am overlooking some other thing .

AppForm.jsx (parent template)

function MainContent(props) {
    const { title, underTitle, children, buttonText, customHandler, bottomPart } = props;
    const [banner, setBanner] = useState({code: null, msg: null})
    
    const reqHandler = async () => {
        customHandler().then((resp) => {
            setBanner({code: 'success', msg: resp.data.msg})
        }).catch((err) => {
            setBanner({ code: 'error', msg: err.response.data.msg })
        });
    }

    return (
        <ThemeProvider theme={formTheme}>
            <Container maxWidth="sm">
                <AppFormPopup statusCode={banner.code} msg={banner.msg} />
                <Box sx={{ mt: 7, mb: 12 }}>
                    <Paper>
                        {children} //displays the fields required by derived form page

                        <Button type='submit' sx={{ mt: 3, mb: 2 }} onClick={reqHandler}>
                            {buttonText}
                        </Button>
                    </Paper>
                </Box>
            </Container>
        </ThemeProvider>
    )
}

function AppForm(props) {
    return ( 
       <>
         <AppFormNav /> 
         <MainContent {...props} />
       </>
    );
}

AppForm.propTypes = {
    children: PropTypes.node,
};

export default AppForm;

AppFormPopup.jsx (child Component which shows popup msg)

function MainContent(props){
    const { msg } = props;

    const [progress, setProgress] = useState(0);
    const [isShown, setIsShown] = useState(true); //state used for controlling visibility of popup msg.

    useEffect(() => {
        const timer = setInterval(() => {
            setProgress((oldProgress) => {
                if(oldProgress === 100){
                    setIsShown(false);
                    // return 0;
                }
                return Math.min(oldProgress + 10, 100);
            });
        }, 500);

        return () => {
            clearInterval(timer);
        };
    });

    return (
        <Grid container sx={{
            display: isShown ? 'block' : 'none',
        }}>
            <Grid item sx={{ color: "white", pl: 1 }}> {msg} </Grid>
        </Grid>
    )
}

export default function AppFormPopup({msg}) {
    if(msg === null) return;
    return (
        <MainContent msg={msg} />
    )
} 

ForgotPass.jsx (form page which derives form template AppForm.jsx)

export default function ForgotPass() {
    const loc = useLocation().pathname;
    const mailRef = useRef(null);
    const reqHandler = async () => {
        const email = mailRef.current.value;
        if(email){
            const resp = await axios.post(loc, {email})
            return resp;
        }
    }

    return (
        <AppForm
            buttonText="Send Reset Link"
            customHandler={reqHandler}
        >
            <Box sx={{ mt: 6 }}>
                <TextField
                    autoFocus
                    label="Email"
                    inputRef={mailRef}
                />
            </Box>

        </AppForm>
    )
}
2 Answers

This is due to the fact that your useEffect will only render once. My suggestion would be to change your useEffect to render if a value change, for example: Let say you got a state name of UserName. Now each time that UserName is being change will rerender your UseEffect function. By adding a [UserName] at end of useEffect it will cause a rerender each time UserName change.

 useEffect(() => {
        const timer = setInterval(() => {
            setProgress((oldProgress) => {
                if(oldProgress === 100){
                    setIsShown(false);
                    // return 0;
                }
                return Math.min(oldProgress + 10, 100);
            });
        }, 500);

        return () => {
            clearInterval(timer);
        };
    },[UserName]);

It feels weird answering your question but here we go. So, thanks @GlenBowler his answer was somewhat right and nudged me towards the right answer.

Instead of modifying of that useEffect, we needed another useEffect to check for changes in msg. So, code should look like this:

AppFormPopup.jsx

function MainContent(props){
    const { msg } = props;

    const [progress, setProgress] = useState(0);
    const [isShown, setIsShown] = useState(true); //state used for controlling visibility of popup msg.

    useEffect(() => {
       setIsShown(true);
       setProgress(0);
    }, [msg]);

    useEffect(() => {
        const timer = setInterval(() => {
            setProgress((oldProgress) => {
                if(oldProgress === 100){
                    setIsShown(false);
                    // return 0;
                }
                return Math.min(oldProgress + 10, 100);
            });
        }, 500);

        return () => {
            clearInterval(timer);
        };
    });

    return (
        <Grid container sx={{
            display: isShown ? 'block' : 'none',
        }}>
            <Grid item sx={{ color: "white", pl: 1 }}> {msg} </Grid>
        </Grid>
    )
}

export default function AppFormPopup({msg}) {
    if(msg === null) return;
    return (
        <MainContent msg={msg} />
    )
} 
Related