Home > Mobile >  Pass value from Timer to its Parent in React
Pass value from Timer to its Parent in React

Time:10-26

I have a small issue and can't get this working properly. I have timer that is going from 10 minutes down, after that I want to update my parent value from true to false. I was trying some solutions that I have found, but I am failing to get this working.

My Timer.tsx:

import React, { useEffect, useState } from "react";

import "./Timer.scss";

interface ITimerProps {
  isValid?: boolean;
}

export const Timer = ({ isValid }: ITimerProps) => {
  const [minutes, setMinutes] = useState(0);
  const [seconds, setSeconds] = useState(0);
  const tokenTTL = 10;
  let difference;

  useEffect(() => {
    const actualTime = new Date();
    const target = new Date(
      actualTime.setMinutes(actualTime.getMinutes()   tokenTTL)
    );

    const interval = setInterval(() => {
      const now = new Date();
      difference = target.getTime() - now.getTime();

      let m: string | number = Math.floor(
        (difference % (1000 * 60 * 60)) / (1000 * 60)
      );
      m = m < 10 ? "0"   m : m;
      setMinutes(m as number);

      let s: string | number = Math.floor((difference % (1000 * 60)) / 1000);
      s = s < 10 ? "0"   s : s;
      setSeconds(s as number);

      if (difference === 0) {
       isValid= true;
      }
    }, 1000);
    return () => clearInterval(interval);
  }, []);

  return (
    <div className="Timer">
      <div className="Timer__Container">
        <div className="Timer-content">
          <span className="minutes">{minutes}</span>
        </div>
        <span className="divider">:</span>
        <div className="Timer-content">
          <span className="seconds">{seconds}</span>
        </div>
      </div>
    </div>
  );
};

I am passing isValid from my parent. So my parent component have a isCodeExpired that I am getting from my hook.

export const Login = () => {
  const { t } = useTranslation();
  const dispatch = useDispatch();
  const logo = ResourceHelper.getResource(ResourceName.APP_LOGO);
  const { code, refreshCode, isCodeExpired, refresh, error, loading } =
    useLoginByCode();

  useEffect(() => {
    refreshCode();
  }, []);

  const handleCancelClick = () => {
    dispatch(goBack());
  };

  const cancelButton = (
    <Button
      forceFocus
      buttonClassName="LoginScreen-button-cancel"
      onClick={handleCancelClick}
    >
      {t("MY_ORDERS__CANCEL")}
    </Button>
  );

  return (
    <>
      {error && !loading ? (
        <ErrorInfo
          className="LoginScreen__Error"
          forceFocus
          onClick={refresh}
          errorMessage={error.Message}
          error={error}
        />
      ) : (
        <div className="LoginScreen">
          <img className="LoginScreen-logo" src={logo} alt="logo" />
          <p className="LoginScreen-info">
            {t("LOGIN_INFO", { LINK: `$t(LOGIN_INFO_LINK)` })}
          </p>
          {loading && (
            <div className="LoginScreen-code">
              <LoaderSpinner />
            </div>
          )}
          {!isCodeExpired && !loading ? (
            <>
              <div className="LoginScreen-code">
                <span>{code}</span>
                <p className="LoginScreen-tokenInfo">
                  {t("TOKEN_VALID_INFO")}
                  {"  "} <Timer isValid={isCodeExpired} />
                </p>
              </div>
              {cancelButton}
            </>
          ) : (
            <>
              <Button
                onClick={refreshCode}
                buttonClassName="LoginScreen-button-refresh"
                disabled={!isCodeExpired}
              >
                {t("REFRESH_CODE")}
              </Button>
              {cancelButton}
            </>
          )}
        </div>
      )}
    </>
  );
};

Right now with this code my isValid is not changed to false. This is some basic issue but I am having issue getting this working.

CodePudding user response:

You can pass a callback function to the child and use the value you get from the child component on your parent component.

import React, { useEffect, useState } from "react";

import "./Timer.scss";

interface ITimerProps {
  isValid?: Function;
}

export const Timer = ({ isValid }: ITimerProps) => {
  const [minutes, setMinutes] = useState(0);
  const [seconds, setSeconds] = useState(0);
  const tokenTTL = 10;
  let difference;

  useEffect(() => {
    const actualTime = new Date();
    const target = new Date(
      actualTime.setMinutes(actualTime.getMinutes()   tokenTTL)
    );

    const interval = setInterval(() => {
      const now = new Date();
      difference = target.getTime() - now.getTime();

      let m: string | number = Math.floor(
        (difference % (1000 * 60 * 60)) / (1000 * 60)
      );
      m = m < 10 ? "0"   m : m;
      setMinutes(m as number);

      let s: string | number = Math.floor((difference % (1000 * 60)) / 1000);
      s = s < 10 ? "0"   s : s;
      setSeconds(s as number);

      if (difference === 0) {
       isValid(true);
      }
    }, 1000);
    return () => clearInterval(interval);
  }, []);

  return (
    <div className="Timer">
      <div className="Timer__Container">
        <div className="Timer-content">
          <span className="minutes">{minutes}</span>
        </div>
        <span className="divider">:</span>
        <div className="Timer-content">
          <span className="seconds">{seconds}</span>
        </div>
      </div>
    </div>
  );
};

You can received the value from your child component like below.

export const Login = () => {
  const { t } = useTranslation();
  const dispatch = useDispatch();
  const [isCodeExpired, setIsCodeExpired]= useState(false);
  const logo = ResourceHelper.getResource(ResourceName.APP_LOGO);
  const { code, refreshCode, refresh, error, loading } =
    useLoginByCode();

  useEffect(() => {
    refreshCode();
  }, []);

  const handleCancelClick = () => {
    dispatch(goBack());
  };

  const cancelButton = (
    <Button
      forceFocus
      buttonClassName="LoginScreen-button-cancel"
      onClick={handleCancelClick}
    >
      {t("MY_ORDERS__CANCEL")}
    </Button>
  );

  return (
    <>
      {error && !loading ? (
        <ErrorInfo
          className="LoginScreen__Error"
          forceFocus
          onClick={refresh}
          errorMessage={error.Message}
          error={error}
        />
      ) : (
        <div className="LoginScreen">
          <img className="LoginScreen-logo" src={logo} alt="logo" />
          <p className="LoginScreen-info">
            {t("LOGIN_INFO", { LINK: `$t(LOGIN_INFO_LINK)` })}
          </p>
          {loading && (
            <div className="LoginScreen-code">
              <LoaderSpinner />
            </div>
          )}
          {!isCodeExpired && !loading ? (
            <>
              <div className="LoginScreen-code">
                <span>{code}</span>
                <p className="LoginScreen-tokenInfo">
                  {t("TOKEN_VALID_INFO")}
                  {"  "} <Timer isValid={(value) => setIsCodeExpired(value)} /> // Do whatever you want with the value you got from your child component
                </p>
              </div>
              {cancelButton}
            </>
          ) : (
            <>
              <Button
                onClick={refreshCode}
                buttonClassName="LoginScreen-button-refresh"
                disabled={!isCodeExpired}
              >
                {t("REFRESH_CODE")}
              </Button>
              {cancelButton}
            </>
          )}
        </div>
      )}
    </>
  );
};

CodePudding user response:

You need to pass in your isValid prop as a callback, since a variable passed value won't update the parent's value.

So rewrite your child component the following way:

interface ITimerProps {
  isValidCb?: (valid: boolean) => void;
}

export const Timer = ({ isValidCb }: ITimerProps) => {
    // Your application code...
    // Call the isValidCb function to update the value in parent
    // And check if it exists, since its optional
    ...
    if (difference === 0 && isValidCb) {
       isValidCb(true);
    }
    ...
}

Then you need to parse a callback function from your parent to the child.

export const Login = () => {
    ...
    <Timer isValid={(isValid: boolean) => isCodeExpired = isValid} />
    ...
}

The callback function has direct access to your parent attributes and can update them. This is one way of updating a parent component from a child component.

  • Related