Infinite loop when set with React Hooks - reactjs

there is such a production in react. I want the user to delete posts that they have shared. I do this:
When the user logs in, I keep the user id as a cookie.
I get the user name of the user that is equivalent to the id in the database with the captured id - HaoAsk
I check with the username of the user who shares the post with the username I received
if it is equal, I allow the user with setOwner.
But it goes into an endless loop.
const user = response.data.filter((dataItem) => (dataItem._id === userid));
const userNickname = JSON.stringify(user.map((value) => { return value.nickName }))
const nickName = userNickname.slice(2, -2)
const deneme1 = posts.filter( (data) => (data.who === nickName))
deneme1 ? setOwner([{ who: nickName, status: true}]) : setOwner([{ status: false }])
console.log(owner)
When I use the following code, everything I write with console.log enters an infinite loop. I couldn't figure it out.
deneme1 ? setOwner ([{who: nickName, status: true}]): setOwner ([{status: false}])
Thanks in advance, Yours!

For any functional component, you normally want to make sure you don't use set outside the event function.
In your case,
const onClick = e => {
// safe to use set method
setOwner()
}
return (
<Component onClick={onClick} user={user} />
)
Because everything inside a functional component is inside the render cycle. It'll run when react try to refresh the display. In your case you set a state variable which triggers the render and then set the variable and then the infinite circle :)
What this means is that you have to find out an event after you initialize your component. In your case, it's a bit tricky, because you want to call this event right away automatically for you.
Please refer to something like this, How to call loading function with React useEffect only once

Related

React-Phone-Number-Input + React-Hook-Form: How to get current country code in controller validation?

I'm using react-phone-number-input library. The phone number input is not required but if the number is present I wish it could be validated before the form is sent.
If the cellphone field is pristine / left untouched when form is submitted then isValid accepts undefined (valid state).
If country code is changed right away (without actually inputting the number) isValid accepts the selected country's calling code (e.g. +46 for Sweden). This is still a perfectly valid case.
When accessed in the isValid function the phoneCountryCode always holds the previous selected value. So there's always a disparity between the validated phone number and the current country code. I'm not sure if the problem is library-specific, maybe it's my mistake. How do I get rid of the mentioned disparity?
I made a reproduction on CodeSandbox.
import PhoneInput, {
parsePhoneNumber,
getCountryCallingCode
} from "react-phone-number-input";
const [phoneCountryCode, phoneCountryCodeSetter] = useState('DE');
<Controller
name="cellphone"
rules={{
validate: {
isValid: value => {
if(value) {
const callingCode = getCountryCallingCode(phoneCountryCode);
if(! new RegExp(`^\\+${callingCode}$`).test(value)) {
// The parsePhoneNumber utility returns null
// if provided with only the calling code
return !!parsePhoneNumber(value);
}
}
return true;
}
}
}}
control={control}
render={({ field }) => (
<PhoneInput
{...field}
onCountryChange={(v) => phoneCountryCodeSetter(v)}
limitMaxLength={true}
international={true}
defaultCountry="DE"
/>
)}
/>
It's a react specific, nothing wrongs in the library. React never update state immediately, state updates in react are asynchronous; when an update is requested there's no guarantee that the updates will be made immediately. Then updater functions enqueue changes to the component state, but react may delay the changes.
for example:
const [age, setAge] = useSate(21);
const handleClick = () => {
setAge(24)
console.log("Age:", age);
}
You'll see 21 logged in the console.
So this is how react works. In your case, as you change country this "onCountryChange" event triggers updating function to update the state and to validate the phone number but the state is not updated yet that's why it is picking the previous countryCode value(Do console log here).
To understand this more clearly put this code inside your component.
useEffect(() => {
console.log("useEffect: phoneCountryCode", phoneCountryCode);
}, [phoneCountryCode]);
console.log(phoneCountryCode, value); // inside isValid()
useEffect callback will be called when phoneCountryCode value is updated and console.log inside isValid() will be called before the state gets updated.
Hopes this makes sense. Happy Coding!!

React JS component declare a variable required to assign value on useEffect to use across multiple functions

In a React Component, Need to declare variable to access across multiple functions. Two approaches tried to achieve this - useState (less good because of rendering) or let/var. Example:
const [userName, setuserName] = useState("");
let root = "";
problem (part 1): on useEffect after setting setuserName hook I can not access userName immediately. but after modifying root I can use the variable immediately. Example:
useEffect(() => {
//get name from firebase doc ref...
let name = user.claims.userName; //"AName"
setuserName(name);
root = name ;
console.log("userName: " + userName+ " , " + root); //here userName is empty but root has the name.
return () => {
// db.ref("").off("value", listener);
};
}, []);
problem (part 2): if both used in a function declared in the component problem part 1 reverses, I mean, the userName (hook) will have name in it but the root variable will be empty. Example:
async function handleSubmit(e) {
// e.preventDefault();
console.log("root = " + root + " , userName : " + userName );//here root is empty but userName has the name.
return; //or call methods to firebase ref.
}
Tested with assigning variables and state with string literals from code within (Eg: root = "testName"). Just need a temporary variable to call firebase functions. Why this behaviour happens and what approach should I use here?
Update:
Re rendering of component will reset the variable, using state hook is kinda overkill but only option.
Now to set the value of state (userName), useEffect is used in the first place. It also calls and fetches other variables from firebase based on the state (userName). If you dont have that reference nothing else works. You dont have that reference because useState will have it on next render, so not usable right here. You can store userName on a variable. Again render will reset that variable, so wont work with variable either. Hope this make sense.
Now I have solved this, thought it would be interesting to put on SO.
first issue is related to the fact the setState doesn't update state synchronous, which is a common doubt, hence console.log will not print next state. And you can't await on some state fwiw, since it's not an actually promise. If you need to perform some action based on a state change you should create another use useEffect with that state as dependency:
useEffect(() => {
//get name from firebase doc ref...
let name = user.claims.userName; //"AName"
setuserName(name);
return () => {
// db.ref("").off("value", listener);
};
}, []);
useEffect(() => {
// another useEffect based on userName as dependency
//do something on userName update...
console.log(userName);
}, [userName]);
second issue is that on rerenders only the state value is reflected correctly. Once setuserName is resolved and your component is rerendered, any declared variables like root will be recreated based on its original logic (unless you memoize it with useMemo to avoid some expensive calculation).
you should better use state across places/functions to control the logic of your component, not variables declared at your component body which would lead to unexpected behaviors.

Timeout not clearing when used in onChange function

I'm creating a search function for a website that I'm working on. When a user types in a keyword, a get request will be sent to retrieve the matching information. However, it feels very wasteful to have it fire every time a key is pressed. Say the user wants to search for sandwiches, they will most likely enter that in pretty quick succession I just want to fire it after a certain amount of time after the user stopped typing(say 250ms). My idea is to set a timeout, which will be cleared on a consecutive keystroke. Unfortunately, the timeout does not reset and just ends up being delayed. I tried having it in a useEffect hook, and then the timeout worked fine but I had some other problems which prompted me to try and do it this way.
const onChangeBrand = (e) => {
const brand = e.target.value
setBrand(brand)
const timeout = setTimeout(()=>{
url.get(`brands?search=${encodeURI(brand.toLowerCase())}&rows=5`)
.then(res => {
setBrands(res.data)
if(res.data.length === 1 && res.data[0].urlEncoding === encodeURI(brand.toLowerCase())){
setPageType("models")
}else{
setPageType("brands")
}
})
},2500)
return () => clearTimeout(timeout);
}
Any help would be much appreciated!
You've got the right idea but the wrong execution. Returning a function from an onChange handler inherently does nothing–this would have worked fine with useEffect so I see where it came from. This pattern is known as throttling / debouncing a function and there are tons of premade libraries out there to help you throttle a function (like lodash.throttle) but it's perfectly cool to spin your own!
The key here would be:
Use a timeout variable that is scoped outside the method
At the start of execution of your onChange, check to see if the timeout variable has a value–if it does, clear it.
Execute onChange, assign new timeout.
You could use a ref or something here but I personally think it's easiest to define your timeout holder outside the scope of your component entirely.
let CHANGE_TIMEOUT = null;
function MyComponent(props) {
// .. component code
const onChangeBrand = (e) => {
if (CHANGE_TIMEOUT) {
// we already have a previous timeout, clear it.
clearTimeout(CHANGE_TIMEOUT);
}
const brand = e.target.value
setBrand(brand)
// Set the timeout again
CHANGE_TIMEOUT = setTimeout(()=>{
url.get(`brands?search=${encodeURI(brand.toLowerCase())}&rows=5`)
.then(res => {
setBrands(res.data)
if(res.data.length === 1 && res.data[0].urlEncoding === encodeURI(brand.toLowerCase())){
setPageType("models")
}else{
setPageType("brands")
}
})
},2500);
}
// .. other component code here
}

Preserve internal state on page refresh in React.js

It must be pretty regular issue.
I'm passing props down to the children and I'm using it there to request to the endpoint. More detailed: I'm clicking on the list item, I'm checking which item was clicked, I'm passing it to the child component and there basing on prop I passed I'd like to request certain data. All works fine and I'm getting what I need, but only for the first time, ie. when refreshing page incoming props are gone and I cannot construct proper URL where as a query I'd like to use the prop value. Is there a way to preserve the prop so when the page will be refresh it will preserve last prop.
Thank you!
(You might want to take a look at: https://github.com/rt2zz/redux-persist, it is one of my favorites)
Just like a normal web application if the user reloads the page you're going to have your code reloaded. The solution is you need to store the critical data somewhere other than the React state if you want it to survive.
Here's a "template" in pseudo code. I just used a "LocalStorage" class that doesn't exist. You could pick whatever method you wanted.
class Persist extends React.Component {
constuctor(props) {
this.state = {
criticalData = null
}
}
componentDidMount() {
//pseudo code
let criticalData = LocalStorage.get('criticalData')
this.setState({
criticalData: criticalData
})
}
_handleCriticalUpdate(update) {
const merge = {
...LocalStorage.get('criticalData')
...update
}
LocalStorage.put('criticalData', merge)
this.setState({
criticalData: merge
})
}
render() {
<div>
...
<button
onClick={e => {
let update = ...my business logic
this._handleCriticalUpdate(update) //instead of set state
}}
>
....
</div>
}
}
By offloading your critical data to a cookie or the local storage you are injecting persistence into the lifecycle of the component. This means when a user refreshes the page you keep your state.
I hope that helps!

Component not updating on stateChange

I would like the value of a TextField to be some computed value... as such, I have a controlled form, ComputedValue, with a render method that looks like this:
render() {
return <TextField
value={this.state.value}
key={this.props.id}
id={this.props.id}
label={this.props.label}
inputProps={{
readOnly: true
}}
/>;
}
the ComputedValue's state.value is set by pulling data from localStorage, doing some computation, and setting the state. That method looks like this:
computeValue() {
let computedValue="";
// split the computation string into constituent components
let splitCS = this.props.computationString.split(/([+,-,*,/,(,),^])/g);
// replace all instaces of questionID's with their value
for (let i = 0; i < splitCS.length; i++) {
if (splitCS[i] !== '+' && splitCS[i] !== '-' && splitCS[i] !== '*' && splitCS[i] !== '/' &&
splitCS[i] !== '(' && splitCS[i] !== ')' && splitCS[i] !=='^') {
// splitCS[i] is a questionID
let Q = getQuestionDataFromLSbyQuestionID(splitCS[i]);
splitCS[i] = Q.value;
}
}
// check that all values returned
if(splitCS.includes("")) {
console.log("null value was returned in ComputedValue question");
} else {
// rejoin string and send to eval (TODO: replace eval)
let finalComputeString = splitCS.join('')
computedValue = eval(finalComputeString);
}
// save value in state
this.setState({
value: computedValue
}, () => {
this.props.stateChangeHandler(this);
this.render();
}
);
return computedValue;
}
This component is leaf in a tree of questions; as their values change (my app syncs LS and the head parent's state) I'd like the computed value to change.
Here are my two issues:
1) Given I call setState inside the computeValue function... I'm unable to call it from within the render function. However, if I only call it from the componentWillMount lifecycle function, it computeValue only gets called once (upon mounting) and then not again. If I call it from the componentWillUpdate, I get an infinite loop. I can put a stop in this loop by comparing nextStates and what not... but that doesn't seem like the best way.
2) renders are not triggered by an update of the parent's state.. including trying a 'forceUpdate') (though they do appear to be triggered by hovering over the mouse and various other UI things).
Given I call setState inside the computeValue function... I'm unable
to call it from within the render function.
I would like you to elaborate or clarify this statment.
However, if I only call it from the componentWillMount lifecycle
function, it computeValue only gets called once (upon mounting) and
then not again.
Yes componentWillMount is a lifecycle hook that is called only once when the component is going to be added in the tree.
If I call it from the componentWillUpdate, I get an infinite loop.
This lifecycle hook is called before your component gets updated. So if you set your state here it will again call this function and it will make a infinite loop.
I can put a stop in this loop by comparing nextStates and what not...
but that doesn't seem like the best way.
You can always do the comparing and update the state. There is nothing like a wrong way in doing so.
renders are not triggered by an update of the parent's state..
including trying a 'forceUpdate')
You should never call render explicitly. It is not a correct practice because it hinders normal flow the react.
I would like you to test the setstate
this.setState(() => ({
value: computedValue
}), () => {
console.log("working")
this.props.stateChangeHandler(this);
}
);
The problem seems to be the () in ({ value: computedValue }). Give a try.
I think your answer is in your second observation. [renders are not triggered by an update of the parent's state]. guess what triggers render from the Parent? props
so, instead of putting your value in the state, put it in the prop, that way if the parent changes it, your component changes.
Hope this helps

Resources