Functional Component: Write functions inside or outside the component? - javascript

I often wrote functional components following a 'Class architecture' where all my function that concern the component are written inside of it like a method in a class.
For example, I have here a function counterAsFloat that is related to the Counter component. As you see I just wrote it inside of the component:
export default function Counter() {
const [counter, setCounter] = React.useState(0);
const counterAsFloat = () => {
return counter.toFixed(2);
};
return (
<div className="counter">
<h1>{counterAsFloat()}</h1>
<button onClick={() => setCounter(counter + 1)}>
Increment
</button>
</div>
);
}
But actually I could also just declare the function outside the component and use it with a parameter:
const counterAsFloat = (counter) => {
return counter.toFixed(2);
};
export default function Counter() {
const [counter, setCounter] = React.useState(0);
return (
<div className="counter">
<h1>{counterAsFloat(counter)}</h1>
<button onClick={() => setCounter(counter + 1)}>
Increment
</button>
</div>
);
}
So are there any pros or cons to write the functions outside the functional component?

This question is pretty opinion-based but there are few notes that you need to think about.
Declaring the function outside of scope is foremost for readability and reusability.
// Reuse logic in other components
const counterAsFloat = (counter) => {
return counter.toFixed(2);
};
// If Counter has complex logic, you sometimes want to compose it
// from functions to make it more readable.
export default function Counter() {
...
return (...);
}
One can argue that the first option is less performant because you declare the function on every render:
export default function Counter() {
...
// declare the function on every render
const counterAsFloat = () => {
return counter.toFixed(2);
};
return (...);
}
Such case is premature optimization. Check out JavaScript closures performance which relates to this.
Note that in this specific case, inlining the function is much better approach.
export default function Counter() {
...
return (
<div>
<h1>{counter.toFixed(2)}</h1>
...
</div>
);
}

While you could want to use outside functions for organization or reusability, it stills seems to go against the structure of functional components, at least for one reason: in functional components, states are immutable. So they normally are constants. And while your 2 functions seem to be somewhat similar, they differ greatly, precisely regarding this specific feature. Take for example this code:
const a = 2;
function increment(){
return ++a;
}
increment();
This is obviously forbidden, you cannot change a constant.
Write it differently:
const a = 2;
function increment(a){
return ++a;
}
increment(a);
The last one is allowed. It won't give the result you expect, at least looking at it rapidly, but it'll compile and won't have any runtime error.
Transpose this to your example. Let's say that you begin by wanting to simply output yourt counter with toFixed(2), so you create an outside function. But then afterwards you decide that over 5 you want to reset the counter. So you do this:
const counterAsFloat = (counter) => {
if(counter > 5){
counter = 0;
}
return counter.toFixed(2);
};
This is going to be allowed, will compile and run. It' won't give the expected result, but it won't be obvious. The inside function could work:
const counterAsFloat = () => {
if(counter > 5){
counter = 0;
}
return counter.toFixed(2);
};
But because in the inside scope counter is a constant you're going to have a compile error or at least a runtime error. That you can quickly fix by replacing counter = 0; by setCounter(0); which is the proper way to handle this requirement.
So in the end, by staying inside your component, it is clearer what the state values are and you're going to have clearer feedback on forbidden manipulations that may be be less obvious with outside functions.
See example with outside function, it is working but doesn't give you the expected result:
const counterAsFloatOutside = (counter) => {
if(counter > 5){
counter = 0;
}
return counter.toFixed(2);
};
function Counter() {
const [counter, setCounter] = React.useState(0);
return (
<div className="counter">
<h1>{counterAsFloatOutside(counter)}</h1>
<button onClick={() => setCounter(counter + 1)}>
Increment
</button>
</div>
);
}
ReactDOM.render(React.createElement(Counter, null), document.body);
<script type="text/javascript" src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script type="text/javascript" src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
With the inside function, it's just not working, which in this case, is preferable. Working with any compiling tool will even give you the error upfront, which is a huge advantage:
function Counter() {
const [counter, setCounter] = React.useState(0);
const counterAsFloat = () => {
if(counter > 5){
counter = 0;
}
return counter.toFixed(2);
};
return (
<div className="counter">
<h1>{counterAsFloat()}</h1>
<button onClick={() => setCounter(counter + 1)}>
Increment
</button>
</div>
);
}
ReactDOM.render(React.createElement(Counter, null), document.body);
<script type="text/javascript" src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script type="text/javascript" src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>

Related

can't get updated state value inside method

I wanna stop the looping when stopState is true.
The stopState is updated when I click the stop button. but inside the startIncrement method, the stopState is always false.
This is my code:
function App() {
const [num, setNum] = useState(0)
const [stop, setStop] = useState(false)
function Delay(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
async function startIncrement(){
for(let i=0; i<100; i++){
console.log(stop) // always false even when i click the stop button
if(stop) i = 2000;
setNum(i)
await Delay(1000)
}
}
return (
<main>
<p>stop: {stop ? "true" : "false"}</p>
<p onClick={startIncrement}>{num}</p>
<button onClick={() => setStop(true)}>stop</button>
</main>
);
}
Short answer:
In order to stop the previously excuted function, you will have to invoke a `clean up` function.
Detail on how it works:
In react, each render has it's own props and state, and we can explain this in action:
First, you excuted the function startIncrement(), at the time of excution, and at that perticular render phase, the value of stop is false;
Every time the num value changes, the page renders, and it just keep going... (excute below code, you can see console prints "renders!")
Then you click setStop(true) button, and at this particular render, stop === true.
However, these two steps involves two different renders, and each renders' state and everything else (props, effect...), does not affect each other, therefore, your stop value in the function never changes.
Here's an alternative to achieve the same:
export default function App() {
const [num, setNum] = useState(0)
const [stop, setStop] = useState(null)
console.log("renders!")
useEffect(() => {
const run = setInterval(() => {
if(stop === false && num < 100) setNum(num + 1);
}, 1000);
return () => clearInterval(run) // clean up function
}, [num, stop]);
return (
<main>
<p>stop: {stop ? "true" : "false"}</p>
<p>{num}</p>
<button onClick={() => setStop(false)}>start</button>
<button onClick={() => setStop(true)}>stop</button>
</main>
);
}
The clean up function in useEffect can be seen as a "undo" function.
Sandbox here, you can read more about side effect from this post in Dan Abramov's blog

Why react useEffect can memorize the state value?

Here's an example:
function Page() {
const [a, setA] = React.useState(0);
useEffect(() => {
const interval = setInterval(() => { console.log(a) }, 2000)
return () => clearInterval(interval)
}, []);
return (
<div>
<span>{a}</span>
<button onClick={() => setA(Math.random())}>button</button>
</div>
);
}
The invertal always log 0 despite the fact that count state variable has actually being increased by clicked the button a few times.
And many people say this is because the interval function capture a. But I really can not undersand it. When a variable can not be found in current environment, it will resolve to parent (or global).
And look at this example in contrast:
function Page() {
let a = 0;
useEffect(() => {
const interval = setInterval(() => { console.log(a) }, 2000)
return () => clearInterval(interval)
}, []);
return (
<div>
<button onClick={() => a = Math.random()}>button</button>
</div>
);
}
This is an obvious example for my expression. The interval closure capture the outer a variable. It will log the fresh value as it changes.
So how react useEffect implement this feature?
As my view (a bad pseudocode):
const hooks = [];
function useState(val) {
let state = hooks[0] || val;
hooks[0] = state;
function setVal(v) {
state = v;
hooks[0] = state;
}
return [state, setVal];
}
let cleanup = null;
function useEffect(callback) {
if (cleanup) cleanup()
cleanup = callback();
}
function Foo() {
const [count, setCount] = useState(0);
useEffect(() => {
setTimeout(() => { console.log(count)}, 1000);
})
return setCount;
}
var setCount = Foo(); // log 0
setCount(1);
Foo(); // log 1
But this is not way react was implement. So how react implement this?
Each time the component is rendered, the function is called.
First a value is assigned to a. This is either 0 (the first time) or the current value from the state (in subsequent times).
const [a, setA] = React.useState(0);
Then there is a useEffect hook. This takes two arguments. A function and a dependency array.
The function will be called every time the values in the dependency array change.
In your example, the dependency array is []. So the values never change. This means the function will only be called on the first render.
During the first render, the function it called. The value of a is 0.
The function passes a function to to setInterval. This function reads a. It has closed over the a variable from the first render.
In subsequent renders of the function, there will be a new a variable (belonging to this call to the function) which is assigned whatever value is in the state. The effect hook doesn't run again though and the existing interval is still looking at the original a variable.
In your second example, you are mutating the value of the original a variable (which is the variable the interval has closed over).

React: Function not reading updated global variable

Run button has already been clicked.
When I click on the skip button I am unable to reach Switch Case 2. The component state skip gets updated but the function still prints the old value of skip.
const Component = () => {
const [skip, setskip] = useState(false);
const [runstate, setrunstate] = useState(1);
const run = async () => {
switch(runstate) {
case 1: {
if(skip) {
setrunstate(2);
}
else {
console.log(skip , "Stuck in Step 1") // false, Stuck in Step 1 even after clicking skip
setTimeout(run, 250)
}
break;
}
case 2: {
console.log("Reached Step 2")
}
}
}
return (
<>
<button onClick={run}> Run </button>
<button onClick={() => setskip(true)}> Skip </button>
</>
)
}
Can anybody tell what might be causing this or a correct way to achieve this?
Why recursion is not working?
There are a lot of factors behind this. So, let's understand with a minimal reproducible example of your use case and breaking the code step-by-step.
const [foo, setFoo] = useState(0);
const recursiveCallback = useCallback(() => {
setFoo(foo + 1);
// an infinite recursion occurs as
// termination condition never satisfies
if (foo <= 2) {
// current reference is being called
// with the current value of foo in
// the scope, i.e. lexical environment
recursiveCallback();
}
}, [foo]);
return (
<>
<p>{foo}</p>
<button onClick={recursiveCallback}>Recursive Callback</button>
</>
);
When recursiveCallback() is called for the very first time, a closure is created with the foo's value being 0.
When foo is incremented with setFoo(foo + 1), a new reference of recursiveCallback is created in the memory (let's say x) which would have the updated value of foo in it.
Now, when recursiveCallback() is called again, it does not call the x reference of itself but the current reference from where it's being called from, which would have the value of foo present in the lexical environment, i.e. 0. So, it appears that foo is not incrementing but in actual it is, as you can see in the <p>{foo}</p>.
So, the solution to fix the above snippet would be to call the function in a useEffect which would always call a new reference of nonRecursiveCallback every time the value of foo is updated!
// note that the function is no more recursive
const nonRecursiveCallback = useCallback(() => {
// updated foo is logged
console.log(foo);
// incrementing foo
setFoo(foo + 1);
}, [foo]);
useEffect(() => {
if (foo && foo <= 2) {
// new reference is being called
// with an updated value of foo
nonRecursiveCallback();
}
}, [foo, nonRecursiveCallback]);
Solution
Instead of calling run() recursively, you can have it called repetitively with the help of useEffect hook unless the termination condition is not satisfied.
const { useState, useEffect, useCallback } = React;
const Component = () => {
const [skip, setSkip] = useState(false);
const [runState, setRunState] = useState(1);
// two additional local states have been introduced
// which would help trigger useEffect repetitively
// after user has clicked on run button
const [runEffect, setRunEffect] = useState(false);
const [toggleEffect, setToggleEffect] = useState(false);
useEffect(() => {
if (runEffect) {
switch (runState) {
case 1: {
if (skip) {
setRunState(2);
} else {
console.log(skip, 'Stuck in Step 1');
// toggle a boolean value which
// would trigger this hook again
setTimeout(() => setToggleEffect(!toggleEffect), 250);
}
break;
}
case 2: {
console.log('Reached Step 2');
setRunEffect(false);
break;
}
default:
break;
}
}
}, [runEffect, toggleEffect, runState, skip]);
const startRecursion = useCallback(() => {
setRunEffect(true);
setToggleEffect(true);
}, []);
const handleSkip = useCallback(() => {
setSkip(true);
}, []);
return (
<React.Fragment>
<button onClick={startRecursion}>Run</button>
<button onClick={handleSkip}>Skip</button>
</ React.Fragment>
);
};
// Render it
ReactDOM.render(
<Component />,
document.getElementById("react")
);
<div id="react"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>

What is the use of functional syntax of setState in react functional components? [duplicate]

This question already has answers here:
Why React useState with functional update form is needed?
(5 answers)
When are functional updates required for computations involving previous state?
(1 answer)
Closed 2 years ago.
we are talking about functional components having useState
lets say
const [age, setAge] = useState(0)
now let us say while updating age I have to use the previous age
React docs mention something called FUNCTIONAL UPDATES where you can pass a function and the argument to that will be the previous value of the state, eg.
setState((previousAge) => previousAge + 1)
why do I need to do this when I can just do
setState(previousAge + 1)
what are the benefits of using functional setState,
I know in class-based components there was something called batching of state updates in the functional way,
but I can't find anything like that in functional components documentation.
They are not the same, if your update depends on a previous value found in the state, then you should use the functional form. If you don't use the functional form in this case then your code will break sometime.
Why does it break and when
React functional components are just closures, the state value that you have in the closure might be outdated - what does this mean is that the value inside the closure does not match the value that is in React state for that component, this could happen in the following cases:
1- async operations (In this example click slow add, and then click multiple times on the add button, you will later see that the state was reseted to what was inside the closure when the slow add button was clicked)
const App = () => {
const [counter, setCounter] = useState(0);
return (
<>
<p>counter {counter} </p>
<button
onClick={() => {
setCounter(counter + 1);
}}
>
immediately add
</button>
<button
onClick={() => {
setTimeout(() => setCounter(counter + 1), 1000);
}}
>
Add
</button>
</>
);
};
2- When you call the update function multiple times in the same closure
const App = () => {
const [counter, setCounter] = useState(0);
return (
<>
<p>counter {counter} </p>
<button
onClick={() => {
setCounter(counter + 1);
setCounter(counter + 1);
}}
>
Add twice
</button>
</>
);
}
Problems might occur depending on how fast/often your setter gets called.
If you are using the simple way by getting the value from the closure, subsequent calls between two renders might not have the desired effect.
A simple example:
function App() {
const [counter, setCounter] = useState(0);
const incWithClosure = () => {
setCounter(counter + 1);
};
const incWithUpdate = () => {
setCounter(oldCounter => oldCounter + 1);
};
return (<>
<button onClick={_ => { incWithClosure(); incWithClosure(); }}>
Increment twice using incWithClosure
</button>
<button onClick={_ => { incWithUpdate(); incWithUpdate(); }}>
Increment twice using incWithUpdate
</button>
<p>{counter}</p>
</>);
}
Both buttons calls one of the increment methods twice.
But we observe:
The first button will increment the counter only by 1
The second button will increment the counter by 2, which is probably the desired outcome.
When can this happen?
Obviously, if incWithClosure is called multiple times immediately after each other
If asynchronous tasks are involved, this can easily happen (see below)
Perhaps, if React has much work to do, its scheduling algorithms may decide to handle multiple very fast clicks using the same event handler
Example with asynchronous work (simulating loading a resource):
function App() {
const [counter, setCounter] = useState(0);
const incWithClosureDelayed = () => {
setTimeout(() => {
setCounter(counter + 1);
}, 1000);
};
const incWithUpdateDelayed = () => {
setTimeout(() => {
setCounter((oldCounter) => oldCounter + 1);
}, 1000);
};
return (
<>
<button onClick={(_) => incWithClosureDelayed()}>
Increment slowly using incWithClosure
</button>
<button onClick={(_) => incWithUpdateDelayed()}>
Increment slowly using incWithUpdate
</button>
<p>{counter}</p>
</>
);
}
Click on the first button twice (within one second) and observe that the counter gets incremented only by 1. The second button has the correct behavior.
Because if you don't you will find at some point that you get an old value for age. The problem is, sometimes what you suggest will work. But sometimes it will not. It may not break in your current code today but it may break in a different code you wrote a few weeks ago or your current code a few months from now.
The symptom is really, really weird. You can print the value of the variable inside a jsx component using the {x} syntax and later print the same variable using console.log after rendering the jsx component (not before) and find that the console.log value is stale - the console.log that happens after the render can somehow have older value than the render.
So the actual value of state variables may not always work correctly in regular code - they are only designed to return the latest value in a render. For this reason the callback mechanism in a state setter was implemented to allow you to get the latest value of a state variable in regular code outside of a render.

How to call a function every x seconds with updated state (React)

Im having a lot of trouble with this and i have tried various things. I want to call a function every second after i have clicked a start button and then have it paused after i click a stop button. I keep getting weird behaviour that i cant explain.
How can i do this in react without classes?
somethings i have treid:
const simulation = () => {
if (!running) {
console.log('hit');
return
} else {
// console.log(grid);
console.log('hey');
setTimeout(simulation, 1000)
}
}
and
enter setInterval(() => {
let newGrid = [...grid]
for (let i = 0; i < numRow; i++) {
for (let k = 0; k < numCol; k++) {
let n = 0;
}
}
console.log(grid);
}, 5000)
I have tried a lot more, In some cases it would update the state should i have added to it but not updated it after i reset the state.
How can i call a function to run every one second with updated values of state * Note the function that i want to run will update the state
You may do the following:
keep track of the current counter value along with the counter on/off state in your component state;
employ useEffect() hook to be called upon turning counter on/off or incrementing that;
within useEffect() body you may call the function, incrementing count by one (if ticking is truthy, hence timer is on) with delayed execution (using setTimeout());
once count variable is changed in the state, useEffect() gets called once again in a loop;
in order to clean up the timer upon component dismantle, you should return a callback, clearing the timer from useEffect()
const { useState, useEffect } = React,
{ render } = ReactDOM,
rootNode = document.getElementById('root')
const App = () => {
const [ticking, setTicking] = useState(true),
[count, setCount] = useState(0)
useEffect(() => {
const timer = setTimeout(() => ticking && setCount(count+1), 1e3)
return () => clearTimeout(timer)
}, [count, ticking])
return (
<div>
<div>{count}</div>
<button onClick={() => setTicking(false)}>pause</button>
<button onClick={() => setTicking(true)}>resume</button>
</div>
)
}
render (
<App />,
rootNode
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.11.0/umd/react-dom.production.min.js"></script><div id="root"></div>
Late reply but maybe interesting for some people.
Look at npm package cron. In this case every 15min As easy as:
const [job] = useState(new cron.CronJob("0 */15 * * * *",async ()=>{
await updateRiderCoords();
}));
useEffect(() => {
job.start();
}, []);

Categories