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.