Reading component state just after setting when using useState hook in react - reactjs

This console.log is not working: It'll just print the previous state value as set is async.
const SomeCompo = () => {
const [count, set] = useState(0);
const setFun = () => {
console.log(count);
set(count + 1);
console.log(count);
}
return <button onClick={setFun}>count: {count}</button>
}
I had to read the count in the render itself:
const SomeCompo = () => {
const [count, set] = useState(0);
console.log(count);
const setFun = () => {
set(count + 1);
}
return <button onClick={setFun}>count: {count}</button>
}
Is there a better way to read the value as I don't want to console for every render.

You can use useEffect for this,
useEffect(() => {
console.log(count);
}, [count]) //[count] is a dependency array, useEffect will run only when count changes.

I would suggest not to use setInterval. I would do something like useEffect. This function will be called each time you do a setState. Just like you had callback after setState. Pass the count state in the array, it will watch only for the count change in the state and console your count.
useEffect(() => {
console.log(count);
}, [count]);
Also if you dont need to rerender your other components, you might wanan use useMemo and useCallback. https://www.youtube.com/watch?v=-Ls48dd-vJE
Here to more read: https://reactjs.org/docs/hooks-effect.html

The way to get a state value is to use useEffect and use the state as a dependency. This means that when we change a value the render cycle will finish and a new one will start, then useEffect will trigger:
useEffect( () => { console.log(value); }, [value] );
If you would need to read the value in the same cycle as it is changed a possibility could be to use the useState set function. This shows the latest value just before updating it:
setValue( latest_value => {
const new_value = latest_value + 1;
console.log(new_value);
return new_value;
} );

Related

Is it okay to change boolean which is a hook and dependency of useEffect in React?

I am working on CSS Transition on React that it automatically unmount after 2 seconds. I am thinking about using useEffect and useState to solve this problem.
I know that changing dependencies inside useEffect causes infinite loop.
For example, the code below will cause infinite loop.
const [count, setCount] = useState(0);
useEffect(() => {
setCount(prev => prev + 1);
},[count]);
But I think infinite loop won't happen if I set dependency to boolean and set if statement inside useEffect just like the code below.
const [showStatus, setshowStatus] = useState(false);
useEffect(() => {
const timeId = setTimeout(() => {
if (showStatus === true){
setshowStatus(false)
}
}, 2000)
return (() => {clearTimeout(timeId)})
}, [showStatus]);
I am relatively new to React so I am worried about this code. Do I have any problems using this code?
I think it's answered here better
React hooks - right way to clear timeouts and intervals
import { useState, useEffect } from "react";
const delay = 5;
export default function App() {
const [show, setShow] = useState(false);
useEffect(
() => {
let timer1 = setTimeout(() => setShow(true), delay * 1000);
// this will clear Timeout
// when component unmount like in willComponentUnmount
// and show will not change to true
return () => {
clearTimeout(timer1);
};
},
// useEffect will run only one time with empty []
// if you pass a value to array,
// like this - [data]
// than clearTimeout will run every time
// this value changes (useEffect re-run)
[]
);
return show ? (
<div>show is true, {delay}seconds passed</div>
) : (
<div>show is false, wait {delay}seconds</div>
);
}

React prop function called after useState set hook kills hook

I have an PostImagesList component that updates when an image is removed. I also send the removed image id to the parent for deletion if the post update is ultimately committed.
I'm trying to understand why a prop function (onRemoveImage) kills the setImages useState hook, unless I remove the same image twice(?) Presently in the parent I am only logging the id sent from the child so nothing is changing with the data.
// <PostImagesList />
const handleRemoveImage = (e, idx, imageId) => {
e.stopPropagation();
let newFilesArr = [...images];
newFilesArr.splice(idx, 1);
setImages(newFilesArr);
onRemoveImage(imageId); // If I comment this out, the setImages updates correctly
};
images is initially set in PostImagesList in a useEffect hook:
useEffect(() => {
setImages(
imagesByPostId?.edges
? imagesByPostId?.edges
.filter(Boolean)
.map((edge) => edge.node)
.filter(Boolean)
: [],
);
};
I also tried setting the images in the declaration instead of in a useEffect hook:
const [images, setImages] = useState(() => {
return imagesByPostId?.edges
? imagesByPostId?.edges
.filter(Boolean)
.map((edge) => edge.node)
.filter(Boolean)
: [];
});
I want to update the list locally in <PostImagesList/>, and send the deleted image id to the parent, but not rerender <PostImagesList/>.
I can't see where you declared setImages but if you want to set the initial state you can do like:
const [state, setState] = React.useState(whatever)
If you want to set it depending on complex conditions, then you can use React.useReducer instead of React.useState or define initial state like:
const [state, setState] = React.useState(() => {
if (itsCool) return "cool";
else return "not cool";
});
If you need to use that useEffect hook and you want to set something at the initial render then you need to pass an empty dependency Array, else you can run into "Maximum update depth exceeded" because setState will call repeatedly. So do it like:
useEffect(() => {
setImages(
imagesByPostId?.edges
? imagesByPostId?.edges
.filter(Boolean)
.map((edge) => edge.node)
.filter(Boolean)
: [],
);
}, []); // here is the empty dependency array
More about useEffect if you are interested:
Note that useEffect is not only componentDidMount but also componentDidUpdate, so the callback function you pass will executed on every component-update. If you pass a dependency array the callback function you pass will get executed only when the dependeny (reference) changes. Something like:
const { useEffect } = (function() {
var _dep = null;
function useEffect(clb, dep) {
if (_dep === null) {
_dep = dep;
clb();
} else if (_dep !== dep) {
_dep = dep;
clb();
} else {
return;
}
}
return {
useEffect
};
})();
var counter = 1;
const cbk = () => console.log(counter);
useEffect(cbk, counter);
useEffect(cbk, counter); // cbk will not execute
useEffect(cbk, counter); // cbk will not execute
counter++;
useEffect(cbk, counter);
to simpily the useEffect funtion we don't pass a dependency array we just pass one dependency.

useState() undefined in React just after set

I have a problem and I do not undersatnd why the hook return undefined :
import React, { useEffect, useState } from 'react';
function App(){
const [globalVariable, setGlobalVariable] = useState();
useEffect(()=>{
const test = 5
console.log(test) // return 5
setGlobalVariable(test)
console.log(globalVariable) // return undefined
},[]);
return (
<div>
</div>
);
}
export default App;
How can I do in order to set directly a new value for the globalVariable ?
As mentioned by other members, setState runs asynchronously in a queue-based system. This means that if you want to perform a state update, this update will be piled up on the queue. However, it does not return a Promise, so you can't use a .then or a wait. Because of this, we need to be aware of possible problems that might occur while manipulating states in React.
Say we have:
// PROBLEM # 1
export function Button() {
const [count, setCount] = useState(0);
function increment() {
setCount(count + 1);
console.log(count); // returns 0
}
return (
<button onClick={increment}>Increment<\button>
)
}
That is basically the same problem you have. Since setCount is asynchronous, the console.log will return the value before the updates (which is 0 here). In your case, it will return undefined because you didn't pass any initial state in the useState hook. Other thing you did different was to fire the state change from inside the useEffect hook, but that doesn't matter for the problem at hand.
To fix this, a good practice is to store the new state in another variable, as follows:
// SOLUTION TO PROBLEM # 1
export function Button() {
const [count, setCount] = useState(0);
function increment() {
const newCountValue = count + 1;
setCount(newCountValue);
console.log(newCountValue); // returns 1
}
return (
<button onClick={increment}>Increment<\button>
)
}
This basically answers your question.
But, there are still other scenarios where state updates might not behave as one would expect. It is important to be aware of these scenarios to avoid possible bugs. For example, let's assume that we are updating a given state multiple times in a row, like:
// PROBLEM # 2
export function Button() {
const [count, setCount] = useState(0);
function increment() {
setCount(count + 1);
setCount(count + 1);
setCount(count + 1);
console.log(count); // returns 0, but the final state will be 1 (not 3)
}
return (
<button onClick={increment}>Increment<\button>
)
}
Here, console.log still returns 0 for the same reason described in Problem #1. But, if you are using the count state somewhere in your app, you will see that the final value after the onClick event is 1, not 3.
Here is how you can fix this:
// SOLUTION TO PROBLEM # 2
export function Button() {
const [count, setCount] = useState(0);
function increment() {
setCount((oldState) => oldState + 1);
setCount((oldState) => oldState + 1);
setCount((oldState) => oldState + 1);
console.log(count); // returns 0, but the final state will be 3
}
return (
<button onClick={increment}>Increment<\button>
)
}
By passing a function to setState, the new state is computed using the previous value on the queue and not the initial one.
As a final example, say we are calling a function right after trying to mutate our state, but this function depends on the state value. Since the operation is asynchronous, that function will use the "old" state value, just like in Problem #1:
// PROBLEM # 3
export function Button() {
const [count, setCount] = useState(0);
onCountChange() {
console.log(count); // returns 0;
}
function increment() {
setCount(count + 1);
onCountChange();
}
return (
<button onClick={increment}>Increment<\button>
)
}
Notice in this problem that we have a function onCountChange that depends on an external variable (count). By external I mean that this variable was not declared inside the scope of onCountChange. They are actually siblings (both lie inside the Button scope). Now, if you are familiar with the concept of "pure functions", "idempotent" and "side effects", you will realize that what we have here is a side-effect issue -- we want something to happen after a state change.
Roughly speaking, whenever you need to handle some side-effects in React, this is done via the useEffect hook. So, to fix our 3rd problem, we can simply do:
// SOLUTION TO PROBLEM # 3
export function Button() {
const [count, setCount] = useState(0);
useEffect(() => {
console.log(count); // returns 0;
}, [count]);
function increment() {
setCount(count + 1);
}
return (
<button onClick={increment}>Increment<\button>
)
}
In the solution above, we moved the content of the onCountChange to useEffect and we added count as a dependency, so whenever it changes, the effect will be triggered.
Because state only has new value when component re-render. So just put console.log(globalVariable) outside useEffect like this:
useEffect(() => {
const test = 5;
console.log(test); // return 5
setGlobalVariable(test);
}, []);
console.log(globalVariable);
Two things:
useState has an initial value, defaulted to undefined.
const [globalVariable, setGlobalVariable] = useState(123); // initial value set to 123
setGlobalVariable is not a synchronous operation. Changing it will not mutate globalVariable immediately. The value in scope will remain the same until the next render phase.
This is because component is not re-rendering. You need to keep the value between the renders and for that you need to change the variable to a state using useState. Check below code for better understanding:
import React, { useEffect, useState } from "react";
function Test() {
const [globalVariable, setGlobalVariable] = useState();
const [test, setTest] = useState(5);
useEffect(() => {
// const test = 5;
// console.log(test); // return 5
setGlobalVariable(test);
console.log(globalVariable); // return undefined
console.log(test); // return 5
}, []);
return <div></div>;
}
export default Test;
This is because React state update is queue based system. It won't update immediately. But if you render a global variable value, you get the required one.
If you have same requirement in class-based component, then you need to pass callback function to setState. It will execute as soon as state is updated.
this.setState(stateupdate, callback)
Once the state is updated, the callback function will execute with the latest state.
const StackOverflow = () => {
const [globalVariable, setGlobalVariable] = useState();
useEffect(()=>{
const test = 5
console.log(test) // return 5
setGlobalVariable(test)
console.log(globalVariable)
},[]);
return (
<Container maxWidth="lg" style={{paddingTop:"5%"}}>
<div>
<h1>StackOverflow - {globalVariable}</h1>
</div>
</Container>
)
}
This is well-explained in the official documentation, §caveats:
The set function only updates the state variable for the next render. If you read the state variable after calling the set function, you will still get the old value that was on the screen before your call.
And especially in §troubleshooting: "I’ve updated the state, but logging gives me the old value"
that's because the setGlobalVariable(test); takes some time, and you are trying to do it in the useEffect, which runs only once, you should use a setTimeOut function to take some time to show the state, try this and let me know is it working or not.
const [globalVariable, setGlobalVariable] = useState();
useEffect(() => {
const test = 5;
// console.log(test); // return 5
setGlobalVariable(test);
setTimeout(() => {
console.log(globalVariable);
}, 2000);
}, []);

useState hook: update a state based on another just updated state

I'm currently looking in to React's funtion Component and the useState hook.
const [stateA, setStateA] = useState(somevalue)
const [stateB, setStateB] = useState(somevalue)
function onClick(value){
setStateA(stateA => transformedValue) //a is now transformedValue
//Use transformedValue to update state B
setStateB(...)
}
My question is how to use the newly updated stateA(transformedValue) to updateB after that.
So far i could only access the previous value of State A, not the transformedValue.
I know i could calculate transformedValue outside of the setStateA function, then use that to setStateB. But is it the correct way?
I know can also merge stateA and stateB into one State too.
What is the correct approach? Is there another more preferred way?
The best approach is using useEffect as mentioned.
useEffect(() => {
setCounter2(count + 1);
}, [count]);
Because of you obsessed with rendering once, try this:
function App() {
const [count, setCount] = useState(0);
const [dummy, setDummy] = useState(0);
useEffect(() => {
console.log(`Renderderd ${count} times`);
});
function handleClick() {
setCount(prevCounter => {
setDummy(prevCounter + 1);
return prevCounter + 1;
});
}
return (
<>
<div>You clicked count {count} times!</div>
<div>Dummy= {dummy} !</div>
<button onClick={handleClick}>Click me for rendering!</button>
</>
);
}
As the method setState is asynchronous.
Your stateA won't update immediately when setState is called.
A way to implement this idea is that
to "watch" a stateA by using useEffect method to ensure stateA is updated.
code seems like this:
useEffect(()=>{
setStateB(stateA); // or do anything with updated stateA
},[stateA]);

Simple counter doesn't stop incrementing inside functional component with useEffect hook

I trying to use rxjs inside a functional component's useEffect Hook.
I believe useEffect can be used just like componentDidMount and componentDidUpdate.
const Counter = () => {
const [state, setState] = useState({counter: 0});
useEffect(() => {
subscriber.subscribe((value) => {
let { counter } = state;
counter += value;
setState({ counter });
});
});
return (
<div>
<span>Counter: {state.counter}</span>
<Crementer />
</div>
);
The subscriber is a simple rxjs subject
const subscriber = new BehaviorSubject(0);
There's also a component to increment / decrement the counter
const Crementer = () => {
return(
<>
<button onClick={() => subscriber.next(1)}>+</button>
<button onClick={() => subscriber.next(-1)}>-</button>
</>
)
};
(I tried counterService.send(1) as well)
The problem is as soon as I click + or - the counter increments or decrements continuously.
You can see the behavior here: https://stackblitz.com/edit/react-9auz4d
Maybe because useEffect runs also on update? It will probably work in a class component with a componentDidMount() ?
Hooks are invoked on every update: https://reactjs.org/docs/hooks-effect.html#explanation-why-effects-run-on-each-update
So you could store the subscription returned form subscribe call and then keep it in another useState. On every effect invocation you'd just check whether it's set or not. Btw, you should also return a teardown function from this effect so that's where you'd need the subscription as well.
Or eventually you can use the second parameter to useEffect to run it just once (How to call loading function with React useEffect only once):
useEffect(() => {
const subscription = observable.subscribe((value) => {
let { counter } = state;
counter += value;
setState({ counter });
});
return () => subscription.unsubscribe();
}, []);

Resources