I really want to understand the lifecycle of react functional component. In many websites you see these three steps:
1-mounthing 2-render 3-unmounthing.
But what about other code that is written before useeffect() function for example assume this:
const Countdown = () => {
let x = 0;
const [countDown, setCountDown] = useState(10)
x = x + 1
if(x > 100) {
x = 0
}
useEffect(() => {
const interval = setInterval(() => {
setCountDown(countDown - 1);
console.log(x)
}, 1000);
}, [countDown]);
};
I want to know :
when countDown state and x variable is declared before useEffect or after it (or inside it)?
when if
or for
phrases is declared(in this example if
phrase), Are they inside useEffect indeed?
what is loading page order? what is start point of executing?
1-mounthing 2-render 4-unmounthing.
It's more like (docs):
useLayoutEffect
)useEffect
))useLayoutEffect
)useEffect
))when countDown state and x variable is declared before useEffect or after it (or inside it)?
Before. The React library can't change how JavaScript code execution occurs. The useState
call and associated declarations are before the useEffect
call, so they happen before it.
when if or for phrases is declared(in this example if phrase), Are they inside useEffect indeed?
No, only the code within the useEffect
callback is called as an effect.
The cycle here is:
x
variable and sets it to 0
.countDown
and setCountDown
and calls useState
, which allocates a state slot in the instance storage; your code stores what useState
returns (the initial state value and the setter) in those constants.x = x + 1
statement runs, updating x
to 1
.if
statement runs, but the condition will never be true — x
is a local variable, not a state member, so its value will always be 1
at this point.useEffect
schedules an effect callback for when countDown
changes.Countdown
.Countdown
should return and commits them to the DOM (making the DOM show what they describe).useEffect
callback (useEffect
callbacks are always called just after mount)
setCountDown
.x
, which will be 1
.setCountDown
, changing the value.x
variable and sets it to 0
.countDown
and setCountDown
and calls useState
, which retrieves the updated state from the instance storage; your code stores what useState
returns (the current state value and the setter) in those constants.x = x + 1
statement runs, updating x
to 1
.if
statement runs, but the condition will never be true.useEffect
schedules an effect callback for when countDown
changes.Countdown
.countDown
changed, React calls your useEffect
callback
setCountDown
.x
, which will be 1
.There are a couple of bugs in the code you've shown
countDown
changes. This will quickly lead to hundreds and then thousands of timers all triggering update calls. You should:
countDown
as a dependency, so the effect only runs on mount. Then use the callback form of setCountDown
.x
to be maintained between calls to the function, but it's a local variable, so it's re-created each time.countDown
reaches 0
, so it will just keep going to -1
, -2
, etc.Here's an updated version with some notes. I was going to remove x
because it wasn't really used for anything, but then thought it might be better to leave it with comments. And I didn't do anything about #4 above, because I wasn't sure what you wanted to do.
const Countdown = () => {
let x = 0;
const [countDown, setCountDown] = useState(10);
x = x + 1;
if (x > 100) { // `x` will always be `1` here, remember that
x = 0; // `x` is a *local variable*
}
useEffect(() => {
const interval = setInterval(() => {
// Use the callback form of the setter so you can update the
// up-to-date value
setCountDown((c) => c - 1);
// Will always show 1
console.log(x);
}, 1000);
// Return a cleanup callback that removes the interval timer
return () => {
clearInterval(interval);
};
}, []);
// ^^ don't use `countDown` as a dependency (in this particular case),
// since we don't use it (anymore, now we use the callback setter)
// Return some elements
return <div>{countDown}</div>;
};
const { useState, useEffect } = React;
const Countdown = () => {
let x = 0;
const [countDown, setCountDown] = useState(10);
x = x + 1;
if (x > 100) { // `x` will always be `1` here, remember that
x = 0; // `x` is a *local variable*
}
useEffect(() => {
const interval = setInterval(() => {
// Use the callback form of the setter so you can update the
// up-to-date value
setCountDown((c) => c - 1);
// Will always show 1
console.log(x);
}, 1000);
// Return a cleanup callback that removes the interval timer
return () => {
clearInterval(interval);
};
}, []);
// ^^ don't use `countDown` as a dependency (in this particular case),
// since we don't use it (anymore, now we use the callback setter)
// Return some elements
return <div>{countDown}</div>;
};
const Example = () => {
return <Countdown />;
};
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<Example />);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>
If we wanted the countdown to stop when it reached 0
and turn off the timer, there are a couple of ways we might do that, see comments in the two live examples showing different ways:
const { useState, useEffect } = React;
const Countdown = () => {
const [countDown, setCountDown] = useState(10);
useEffect(() => {
const interval = setInterval(() => {
// We could cancel the interval from within the setter
// callback. It's a bit dodgy, though, to have side-
// effects in setter callbacks.
setCountDown((c) => {
const updated = c - 1;
if (updated === 0) {
clearInterval(interval);
}
return updated;
});
}, 1000);
return () => {
clearInterval(interval);
};
}, []);
return <div>{countDown === 0 ? "Done" : countDown}</div>;
};
const Example = () => {
return <Countdown />;
};
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<Example />);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>
const { useState, useEffect, useRef } = React;
const Countdown = () => {
// We could store the timer handle in a ref (which is maintained
// across renders) and use a second `useEffect` to cancel it when
// `countDown` reaches zero.
const intervalRef = useRef(0);
const [countDown, setCountDown] = useState(10);
useEffect(() => {
intervalRef.current = setInterval(() => {
setCountDown((c) => c - 1);
}, 1000);
return () => {
// (It's okay if this tries to clear an interval that
// isn't running anymore.)
clearInterval(intervalRef.current);
};
}, []);
useEffect(() => {
if (countDown === 0) {
clearInterval(intervalRef.current);
}
}, [countDown]);
return <div>{countDown === 0 ? "Done" : countDown}</div>;
};
const Example = () => {
return <Countdown />;
};
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<Example />);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>