I'm trying to re-render a component to hide or show a 'Use current location' button when the user blocks or allows their location to be known (by clicking on the info icon to the left of the browser address in Chrome). I'm totally confused by why the first example below works, (ie. the button toggled appropriately as soon as the permission is changed) but the second doesn't (it requires a refresh).
I was attemping to remove the duplicate permission.state !== 'denied' by simply defining the constant hasPermission.
Also, I don't clean up the onchange listener. How do I do this? Can I just assign to null or delete the property?
Works:
useEffect(() => {
navigator.permissions.query({ name: 'geolocation' }).then(permission => {
setHasPermission(permission.state !== 'denied')
permission.onchange = () =>
setHasPermission(permission.state !== 'denied')
})
}, [])
Doesn't work:
useEffect(() => {
navigator.permissions.query({ name: 'geolocation' }).then(permission => {
const hasPermission = permission.state !== 'denied';
setHasPermission(hasPermission)
permission.onchange = () =>
setHasPermission(hasPermission)
})
}, [])
Basically, permission.onchange is defined only once.
So, when you define, in your second example, permission.onchange = () => setHasPermission(hasPermission) you create an event listener that will always call setHasPermission with the same value: the result of permission.state !== 'denied' saved above.
In your first example, it works because permission.state !== 'denied' is evaluated in the event callback.
Is it clear enough ?
For cleanup, useEffect can return a function that will be called when component unmounts. Please check https://reactjs.org/docs/hooks-effect.html#example-using-hooks-1
Related
I have been trying to figure something out in my React cryptocurrency project. So I have a function that basically polls for a response from the api every 6 seconds. It is supposed to start polling the moment the component renders, so I have it in a useEffect. It has default parameters being passed in for the polling to happen right after render. The polling works in terms that it polls and get the response based on the default values. It also takes in the user inputs and returns a response based on it, however, after the next poll, the arguments being passed to the api for polling are back to their defaults. In other words, the arguments being passed into the polling function on the next polls are all back to the default values I passed in, and not a continuation of the current state of the arguments.
Here is where I define pair, which lives above the useEffect:
const baseAsset = transactionType === TRANSACTION_TYPES.BUY ? selectedCurrencyState.selectedToCurrency : selectedCurrencyState.selectedFromCurrency;
const quoteAsset = transactionType === TRANSACTION_TYPES.SELL ? selectedCurrencyState.selectedToCurrency : selectedCurrencyState.selectedFromCurrency;
const pair = baseAsset && quoteAsset ? `${baseAsset}/${quoteAsset}` : '';
Here is the function that gets polled:
const handleInitPoll = useCallback((baseAndQuote, side, value) => {
setIsLoading(true);
getSwapPrice(baseAndQuote, side, value || 0)
.then((res) => {
if (res.error) {
setErrorMessage(res.error);
} else if (res.price) {
setIsLoading(false);
setSwapPriceInfo(res);
}
});
}, [pair, transactionType, fromCurrencyAmount]);
And here is the useEffect that polls it:
useEffect(() => {
if (isLoggedIn) {
getSwapPairs()
.then((res) => {
setSwapInfo(res.markets);
// Set state of selected currencies on render
if (transactionType === TRANSACTION_TYPES.BUY) {
setSelectedCurrencyState({
...selectedCurrencyState,
selectedToCurrency: (quoteAsset !== symbol) ? symbol : ''
});
}
});
if (symbol === selectedCurrencyState.selectedToCurrency) {
actions.updateChartQuote(selectedCurrencyState.selectedFromCurrency);
}
if (pair && transactionType && symbol === baseAsset) {
handleInitPoll(pair, transactionType, fromCurrencyAmount);
}
const timer = setInterval(handleInitPoll, 6000, pair, transactionType, fromCurrencyAmount);
return () => {
clearInterval(timer);
};
}
setSelectedCurrencyState({ ...selectedCurrencyState, selectedFromCurrency: 'SHIB', selectedToCurrency: 'DASH' });
}, [pair, transactionType, fromCurrencyAmount, symbol]);
Here is the debouncing method:
const debounceOnChange = useCallback(debounce(handleInitPoll, 500, pair, transactionType, fromCurrencyAmount), []);
And here is where the user is entering the input to debounce the api call when a user input is detected onChange:
const handleAssetAmount = (e) => {
const { value } = e.target;
const formattedAmount = handleAssetAmountFormat(value);
setFromCurrencyAmount(formattedAmount);
validateInputAmount(formattedAmount);
debounceOnChange(pair, transactionType, formattedAmount);
};
You should put pair in a useState hook. otherwise, everytime when this component is redenrering. the following part will be executed:
const pair = baseAsset && quoteAsset ? `${baseAsset}/${quoteAsset}` : '';
That's why you got a initial value again.
useState can help you to preserve the value for the whole component lifecircle unless you call set***().
I have a local state selectedProducts holding a number representing the number of products the client has selected, while exiting the screen I want to update the number in redux so it can be viewed in the cart on a different screen.
but every time my local selectedProducts updates it stacks another function call on beforeRemove event
I know this is a noob problem but I've spent hours trying to find a solution to this problem and advice would be very helpful :)
useEffect(()=>{
navigation.addListener('beforeRemove', () => {
console.log('check this', selectedProducts);
if (!selectedProducts) {
return;
}
dispatch(addToCartMultipleQty(selectedProducts));
});
},[selectedProducts])
selectedProducts is a number state whose initial value is null and on a button click event its value is either incremented or decremented by 1, if its previous value is null then its value is set to 1
Note: I just want to update selectedProducts state's latest value only once in redux when the screen is about to be exited/unmonted
You can try this:
useEffect(()=>{
navigation.addListener('beforeRemove', () => {
console.log('check this', selectedProducts);
if (!selectedProducts) {
return;
}
dispatch(addToCartMultipleQty(selectedProducts));
});
return () => {
navigation.removeListener('beforeRemove');
}
}, [selectedProducts])
Add that in return at the end of useEffect it will work as componentWillUnmount in functional component
useEffect(() => {
return () => {
// Anything in here is fired on component unmount.
}
}, [])
Edit: In Your case
useEffect(() => {
console.log('check this', selectedProducts);
if (!selectedProducts) {
return;
}
return () => {
// Anything in here is fired on component unmount.
if (selectedProducts) {
dispatch(addToCartMultipleQty(selectedProducts));
}
};
}, [selectedProducts]);
In my app you can set an age restriction on a link. If there is an age restriction on the link will it have an item named ageRestriction in the link state which contains an int which is the age. If it does not have an age restriction there will be none.
When i chance rather or not the link has an age restriction the follow code runs:
useEffect(async () => {
if(ageRestriction === false) {
const newLink = link;
delete newLink.ageRestriction
setLink(newLink)
}
else if(ageRestriction === true) setLink({...link, ageRestriction: storedAge})
}, [ageRestriction])
which should then trigger the following code:
useEffect(() => {
const validate = validateLink(link)
if(validate) {
setError(validate)
}
setError({})
dispatch({type: 'update', link: link, index: index})
}, [link])
If I add an age restriction in the first piece of code the useEffect in the second piece of code trigger. If I delete the ageRestriction item from the object and set the state the useEffect in the second piece of code does not trigger. What can I do to fix this thanks! in advance.
The dependency of your second useEffect should be link.ageRestriction because the comparison between objects is done by reference, thus removing the property from the link object doesn't trigger the effect:
useEffect(() => {
const validate = validateLink(link)
if(validate) {
setError(validate)
}
setError({})
dispatch({type: 'update', link: link, index: index})
}, [link.ageRestriction])
Alternatively, you can create a new object when removing the property in the first useEffect as follow:
useEffect(async () => {
if(ageRestriction === false) {
const newLink = link;
delete newLink.ageRestriction
setLink({ ...newLink })
}
else if(ageRestriction === true) setLink({...link, ageRestriction: storedAge})
}, [ageRestriction])
I am developing a React Native application and am facing the following error:
I have defined a useRef which stores the doc ID from a firebase collection. But when I call that variable after it has been defined, the .current value returns a blank string.
db.collection('users').onSnapshot((snapshot) => {
snapshot.docs.map((doc) => {
if (doc.data().email === auth.currentUser?.email) {
bidId.current = doc.id
console.log(bidId.current)
}
})
})
The above code returns the expected value. However, when I call the variable outside this db.collection loop, I get the following value:
But calling the bidId.current returns a blank string.
Please can someone help me with this. Thanks!
Actually this is what happens:
db.collection('users').onSnapshot((snapshot) => {
snapshot.docs.map((doc) => {
if (doc.data().email === auth.currentUser?.email) {
bidId.current = doc.id
// This line gets executed after some time!
console.log(bidId.current)
}
})
})
// This gets executed first! (The value has not been stored yet!)
console.log(bidId.current);
Using the "useState" hook instead of "useRef" will solve the issue. Consider the following code:
const [BidId, setBidId] = useState<string | null>(null);
// This will store the value...
useEffect(() => {
db.collection('users').onSnapshot((snapshot) => {
snapshot.docs.map((doc) => {
if (doc.data().email === auth.currentUser?.email) {
setBidId(doc.id);
}
})
})
}, []);
// Here you can access the value
useEffect(() => {
if(BidId !== null)
console.log(BidId);
}, [BidId]);
// You can also return the component like the following:
return (<View>The Bid ID is: {BidId !== null ? BidId : "Loading..."}</View>);
Your useEffect basically says that whenever pageRef changes, call this function. If done outside, it will call do your tasks on every render instead of doing the whenever pageRef values is changed. Also, in initial renders, it may give undefined values.
You can only return a function in useEffect which basically says that before running the same next time, run this function before.
Try (currentUser without the '?' query character):
if (doc.data().email === auth.currentUser.email) {
bidId.current = doc.id
console.log(bidId.current)
}
Is one allowed to call a setState inside the function passed into setState. for instance im trying to check for errors in user input and im calling the error checker function inside the setInpuForm, I know I can call the checkerror() from outside the setInputForm i.e inside the change handler, but out of curiosity, i decided to try it this way. But I discovered after typing the first character into the input field no changes but from the second entry up wards,its displayed. When i ran this it worked, the issue is that the very first time the input changed, the value was not updated but subsequently it worked
const errorChecker = (inputValue, element)=>{
//check for password error
if (element==='password') {
if (inputValue.length<=6) {
console.log('function error checker called')
setInputForm((form)=>{
console.log('after error check')
console.log(form.password.value)
return {
...form,
[element]: {
...form[element],
error: 'Password can not be less than 6'
}
}
})
} else {
setInputForm((form) => {
return {
...form,
[element]: {
...form[element],
error: ''
}
}
})
}
}}
<Input
key={inputForm[it].label}
changed={(event) => {
const val = event.target.value
setInputForm((form)=>{
console.log(form.password.value)
console.log('before error check')
errorChecker(val, it)
console.log('from set valuie')
return {
...form,
[it]: {
...form[it],
value: val
}
}
})
}}
I believe your issue is that you are calling two setState functions one after another, both of which rely on the previousState value. setState as you may know is not synchronous, and needs some time to finish its execution. In the meantime what you would be using in your errorChecker function would be the last state registered.
In order to avoid this, in a class based component you would usually include a callback function on your this.setState function. In a functional component you cannot do that. Instead you can use useEffect in order to detect changes to a certain state, and trigger a function like errorChecker based on those changes.
For example:
useEffect(() => {
if (inputForm[it].value !== "") {
errorChecker(inputForm[it].value, inputForm[it]);
}
}, [inputForm[it].value);