Search code examples
reactjsstatereact-propsrerender

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


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

Solution

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