skip to Main Content

I have an issue with my project.
I need to pass the state of a countdown Child component to a Parent component, to make the Parent component visible or not if the time is 0.

this is my countdown component

import { Typography, Paper, Grid } from '@mui/material';
import React, { useEffect, useRef, useState } from 'react';

const formatTime = (time) => {
  let minutes = Math.floor(time / 60);
  let seconds = Math.floor(time - minutes * 60);

  if (minutes <= 10) {
    minutes = '0' + minutes;
  }
  if (seconds <= 10) {
    seconds = '0' + seconds;
  }
  return minutes + ':' + seconds;
};

function CountDown(props) {
  const [countdown, setCountdown] = useState(props.seconds);
  const [onTime, setOnTime] = useState(true);
  const timertId = useRef();

  useEffect(() => {
    timertId.current = setInterval(() => {
      setCountdown((prev) => prev - 1);
    }, 1000);
    return () => clearInterval(timertId.current);
  }, []);

  useEffect(() => {
    if (countdown <= 0) {
      clearInterval(timertId.current);
      setOnTime(false);
    }
  }, [countdown]);

  return (
    <Grid container>
      <Grid item xs={5}>
        <Paper elevation={0} variant='outlined' square>
          <Typography component='h6' fontFamily={'Roboto'}>
            Timer:
          </Typography>
        </Paper>
      </Grid>
      <Grid item xs={5}>
        <Paper
          elevation={0}
          variant='outlined'
          square
          sx={{ bgcolor: 'lightblue' }}
        >
          <Typography component='h6' fontFamily={'Roboto'}>
            {formatTime(countdown)}
          </Typography>
        </Paper>
      </Grid>
    </Grid>
  );
}

export default CountDown;

I want to pass the OnTime state to the Parent component:

export default function QuestionCard() {
  const [questions, setQuestions] = useState([]);
  const [clickedIndex, setClickedIndex] = useState(0);
  const [currentQuestionIndex, setCurrentQuestionIndex] = useState(0);
  const [value, setValue] = useState(null);
  const { isLoading, error, sendRequest: getQuestions } = useAxios();
  const { sendRequest: getAnswers } = useAxios();

  const handleSubmit = () => {
    setValue(true);
  };

  const handleSelectedItem = (index) => {
    setClickedIndex(index);
  };

  const handleChange = (e) => {
    setValue(e.target.value);
  };

  useEffect(() => {
    const transformQuestions = (questionObj) => {
      const loadedQuestions = [];

      for (const questionKey in questionObj) {
        loadedQuestions.push({
          id: questionKey,
          id_test: questionObj[questionKey].id_test,
          tipologia_domanda: questionObj[questionKey].tipologia_domanda,
          testo: questionObj[questionKey].testo,
          immagine: questionObj[questionKey].immagine,
          eliminata: questionObj[questionKey].eliminata,
        });
      }
      setQuestions(loadedQuestions);
    };
    getQuestions(
      {
        method: 'GET',
        url: baseURL_Q,
      },
      transformQuestions
    );
  }, [getQuestions]);

  let questionsTitle = questions.map((element) => `${element.testo}`);
  let questionId = questions.map((element) => `${element.id}`);

  return (
    <Grid container spacing={1}>
      <Grid item xs={10}>
        <Box
          sx={{
            minWidth: 275,
            display: 'flex',
            alignItems: 'center',
            paddingLeft: '50%',
            paddingBottom: '5%',
            position: 'center',
          }}
        >
          <Card
            variant='outlined'
            sx={{
              minWidth: 400,
            }}
          >
            <CardContent>
              <Grid container spacing={0}>
                <Grid item xs={8}>
                  <Typography
                    variant='h5'
                    component='div'
                    fontFamily={'Roboto'}
                  >
                    Nome Test
                  </Typography>
                </Grid>
                <Grid item xs={4}>
                  <CountDown seconds={300} />
                </Grid>
              </Grid>

              <LinearProgress variant='determinate' value={1} />

              <Typography
                sx={{ mb: 1.5, mt: 1.5 }}
                fontFamily={'Roboto'}
                fontWeight={'bold'}
              >
                {questionsTitle[currentQuestionIndex]}
              </Typography>

              <ButtonGroup
                fullWidth
                orientation='vertical'
                onClick={handleSubmit}
                onChange={handleChange}
              >
                <ListItemButton
                  selected={clickedIndex === 1}
                  onClick={() => handleSelectedItem(1)}
                >
                  Risposta 1
                </ListItemButton>
                <ListItemButton
                  selected={clickedIndex === 2}
                  onClick={() => handleSelectedItem(2)}
                >
                  Risposta 2
                </ListItemButton>
                <ListItemButton
                  selected={clickedIndex === 3}
                  onClick={() => handleSelectedItem(3)}
                >
                  Risposta 3
                </ListItemButton>
                <ListItemButton
                  selected={clickedIndex === 4}
                  onClick={() => handleSelectedItem(4)}
                >
                  Risposta 4
                </ListItemButton>
              </ButtonGroup>
            </CardContent>
            <CardActions>
              <Button onClick={goToNext} disabled={!value} variant='contained' size='small'>
                Avanti
              </Button>
            </CardActions>
          </Card>
        </Box>
      </Grid>
    </Grid>
  );
}

The idea is that onTime is true, the QuestionCard gets rendered with question and answers, if onTime is false, it gets rendered an alert or a text. How can I pass the onTime state from Countdown to QuestionCard? Thank you

3

Answers


  1. Move your onTime hook to the parent object (QuestionCard), and pass the setter function setOntime to the child.

    Since you aren’t using onTime in CountDown, it doesn’t make sense to define it there. Using the method described above will move your state object to where it needs to be, an allow greater control by passing the setter function to where it is required.

    Login or Signup to reply.
  2. I think the best practice here is to manage the onTime state in the parent, and pass the setOnTime function from the Parent to the CountDown component through the props, so it will be able to change the onTime state in the parent.

    Suggestion for implementation:

    export default function QuestionCard() {
      const [questions, setQuestions] = useState([]);
      const [clickedIndex, setClickedIndex] = useState(0);
      const [currentQuestionIndex, setCurrentQuestionIndex] = useState(0);
      const [value, setValue] = useState(null);
      const { isLoading, error, sendRequest: getQuestions } = useAxios();
      const { sendRequest: getAnswers } = useAxios();
      
      const handleSubmit = () => {
        setValue(true);
      };
    
      const handleSelectedItem = (index) => {
        setClickedIndex(index);
      };
    
      const handleChange = (e) => {
        setValue(e.target.value);
      };
    
      useEffect(() => {
        const transformQuestions = (questionObj) => {
          const loadedQuestions = [];
    
          for (const questionKey in questionObj) {
            loadedQuestions.push({
              id: questionKey,
              id_test: questionObj[questionKey].id_test,
              tipologia_domanda: questionObj[questionKey].tipologia_domanda,
              testo: questionObj[questionKey].testo,
              immagine: questionObj[questionKey].immagine,
              eliminata: questionObj[questionKey].eliminata,
            });
          }
          setQuestions(loadedQuestions);
        };
        getQuestions(
          {
            method: 'GET',
            url: baseURL_Q,
          },
          transformQuestions
        );
      }, [getQuestions]);
    
      let questionsTitle = questions.map((element) => `${element.testo}`);
      let questionId = questions.map((element) => `${element.id}`);
    
      return (
        <Grid container spacing={1}>
          <Grid item xs={10}>
            <Box
              sx={{
                minWidth: 275,
                display: 'flex',
                alignItems: 'center',
                paddingLeft: '50%',
                paddingBottom: '5%',
                position: 'center',
              }}
            >
              <Card
                variant='outlined'
                sx={{
                  minWidth: 400,
                }}
              >
                <CardContent>
                  <Grid container spacing={0}>
                    <Grid item xs={8}>
                      <Typography
                        variant='h5'
                        component='div'
                        fontFamily={'Roboto'}
                      >
                        Nome Test
                      </Typography>
                    </Grid>
                    <Grid item xs={4}>
                      <CountDown seconds={300} setOnTime={setOnTime} />
                    </Grid>
                  </Grid>
    
                  <LinearProgress variant='determinate' value={1} />
    
                  <Typography
                    sx={{ mb: 1.5, mt: 1.5 }}
                    fontFamily={'Roboto'}
                    fontWeight={'bold'}
                  >
                    {questionsTitle[currentQuestionIndex]}
                  </Typography>
    
                  <ButtonGroup
                    fullWidth
                    orientation='vertical'
                    onClick={handleSubmit}
                    onChange={handleChange}
                  >
                    <ListItemButton
                      selected={clickedIndex === 1}
                      onClick={() => handleSelectedItem(1)}
                    >
                      Risposta 1
                    </ListItemButton>
                    <ListItemButton
                      selected={clickedIndex === 2}
                      onClick={() => handleSelectedItem(2)}
                    >
                      Risposta 2
                    </ListItemButton>
                    <ListItemButton
                      selected={clickedIndex === 3}
                      onClick={() => handleSelectedItem(3)}
                    >
                      Risposta 3
                    </ListItemButton>
                    <ListItemButton
                      selected={clickedIndex === 4}
                      onClick={() => handleSelectedItem(4)}
                    >
                      Risposta 4
                    </ListItemButton>
                  </ButtonGroup>
                </CardContent>
                <CardActions>
                  <Button onClick={goToNext} disabled={!value} variant='contained' size='small'>
                    Avanti
                  </Button>
                </CardActions>
              </Card>
            </Box>
          </Grid>
        </Grid>
      );
    }
    
    import { Typography, Paper, Grid } from '@mui/material';
    import React, { useEffect, useRef, useState } from 'react';
    
    const formatTime = (time) => {
      let minutes = Math.floor(time / 60);
      let seconds = Math.floor(time - minutes * 60);
    
      if (minutes <= 10) {
        minutes = '0' + minutes;
      }
      if (seconds <= 10) {
        seconds = '0' + seconds;
      }
      return minutes + ':' + seconds;
    };
    
    function CountDown(props) {
      const [countdown, setCountdown] = useState(props.seconds);
      const timertId = useRef();
    
      useEffect(() => {
        timertId.current = setInterval(() => {
          setCountdown((prev) => prev - 1);
        }, 1000);
        return () => clearInterval(timertId.current);
      }, []);
    
      useEffect(() => {
        if (countdown <= 0) {
          clearInterval(timertId.current);
          props.setOnTime(false);
        }
      }, [countdown, props.setOnTime]);
    
      return (
        <Grid container>
          <Grid item xs={5}>
            <Paper elevation={0} variant='outlined' square>
              <Typography component='h6' fontFamily={'Roboto'}>
                Timer:
              </Typography>
            </Paper>
          </Grid>
          <Grid item xs={5}>
            <Paper
              elevation={0}
              variant='outlined'
              square
              sx={{ bgcolor: 'lightblue' }}
            >
              <Typography component='h6' fontFamily={'Roboto'}>
                {formatTime(countdown)}
              </Typography>
            </Paper>
          </Grid>
        </Grid>
      );
    }
    
    export default CountDown;
    
    Login or Signup to reply.
  3. Just create a new state in your parent component and pass its setter function to the child component and now you can update a parent’s state in the child component. Please check below code

    Your child component

    useEffect(() => {
        if (countdown <= 0) {
          clearInterval(timertId.current);
          setOnTime(false);
          setOnTimeFromCountdown(false);//set the value here and now you can access this value in your parent component
        }
      }, [countdown]);
    
      return (
        <Grid container>
          <Grid item xs={5}>
            <Paper elevation={0} variant='outlined' square>
              <Typography component='h6' fontFamily={'Roboto'}>
                Timer:
              </Typography>
            </Paper>
          </Grid>
          <Grid item xs={5}>
            <Paper
              elevation={0}
              variant='outlined'
              square
              sx={{ bgcolor: 'lightblue' }}
            >
              <Typography component='h6' fontFamily={'Roboto'}>
                {formatTime(countdown)}
              </Typography>
            </Paper>
          </Grid>
        </Grid>
      );
    }
    
    export default CountDown;
    
    

    Your parent component

    export default function QuestionCard() {
      const [questions, setQuestions] = useState([]);
      const [clickedIndex, setClickedIndex] = useState(0);
      const [currentQuestionIndex, setCurrentQuestionIndex] = useState(0);
      const [value, setValue] = useState(null);
      const { isLoading, error, sendRequest: getQuestions } = useAxios();
      const { sendRequest: getAnswers } = useAxios();
      const [onTimeFromCountdown, setOnTimeFromCountdown] = useState(true);//add this state; you can set any initial value as per your requirement
    
      const handleSubmit = () => {
        setValue(true);
      };
    
      const handleSelectedItem = (index) => {
        setClickedIndex(index);
      };
    
      const handleChange = (e) => {
        setValue(e.target.value);
      };
    
      useEffect(() => {
        const transformQuestions = (questionObj) => {
          const loadedQuestions = [];
    
          for (const questionKey in questionObj) {
            loadedQuestions.push({
              id: questionKey,
              id_test: questionObj[questionKey].id_test,
              tipologia_domanda: questionObj[questionKey].tipologia_domanda,
              testo: questionObj[questionKey].testo,
              immagine: questionObj[questionKey].immagine,
              eliminata: questionObj[questionKey].eliminata,
            });
          }
          setQuestions(loadedQuestions);
        };
        getQuestions(
          {
            method: 'GET',
            url: baseURL_Q,
          },
          transformQuestions
        );
      }, [getQuestions]);
    
      let questionsTitle = questions.map((element) => `${element.testo}`);
      let questionId = questions.map((element) => `${element.id}`);
    
      return (
        <Grid container spacing={1}>
          <Grid item xs={10}>
            <Box
              sx={{
                minWidth: 275,
                display: 'flex',
                alignItems: 'center',
                paddingLeft: '50%',
                paddingBottom: '5%',
                position: 'center',
              }}
            >
              <Card
                variant='outlined'
                sx={{
                  minWidth: 400,
                }}
              >
                <CardContent>
                  <Grid container spacing={0}>
                    <Grid item xs={8}>
                      <Typography
                        variant='h5'
                        component='div'
                        fontFamily={'Roboto'}
                      >
                        Nome Test
                      </Typography>
                    </Grid>
                    <Grid item xs={4}>
                      <CountDown setOnTimeFromCountdown={setOnTimeFromCountdown} seconds={300} />
                    </Grid>
                  </Grid>
    
                  <LinearProgress variant='determinate' value={1} />
    
                  <Typography
                    sx={{ mb: 1.5, mt: 1.5 }}
                    fontFamily={'Roboto'}
                    fontWeight={'bold'}
                  >
                    {questionsTitle[currentQuestionIndex]}
                  </Typography>
    
                  <ButtonGroup
                    fullWidth
                    orientation='vertical'
                    onClick={handleSubmit}
                    onChange={handleChange}
                  >
                    <ListItemButton
                      selected={clickedIndex === 1}
                      onClick={() => handleSelectedItem(1)}
                    >
                      Risposta 1
                    </ListItemButton>
                    <ListItemButton
                      selected={clickedIndex === 2}
                      onClick={() => handleSelectedItem(2)}
                    >
                      Risposta 2
                    </ListItemButton>
                    <ListItemButton
                      selected={clickedIndex === 3}
                      onClick={() => handleSelectedItem(3)}
                    >
                      Risposta 3
                    </ListItemButton>
                    <ListItemButton
                      selected={clickedIndex === 4}
                      onClick={() => handleSelectedItem(4)}
                    >
                      Risposta 4
                    </ListItemButton>
                  </ButtonGroup>
                </CardContent>
                <CardActions>
                  <Button onClick={goToNext} disabled={!value} variant='contained' size='small'>
                    Avanti
                  </Button>
                </CardActions>
              </Card>
            </Box>
          </Grid>
        </Grid>
      );
    }
    

    Happy Coding 😉

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search