Is there a way to initiate state to true, then change to false after x amount of time within useState?
Currently looks like this:
const [scrollDown, setScrollDown] = useState(true)
setTimeout(() => {
setScrollDown(false)
}, 2000)
Looking to do something like this, but syntax doesn't appear to be correct:
const [scrollDown, setScrollDown] = useState(() => {
setScrollDown(true)
setTimeout(() => {
setScrollDown(false)
}, 2000)
})
Trying this as well:
const [scrollDown, setScrollDown] = useState(true, () => {
setTimeout(() => {
setScrollDown(false)
}, 1000)
})
Like others have mentioned, using useEffect works best so I went with this solution. My issue initially was passing that second empty array arg:
const [scrollDown, setScrollDown] = useState(true)
useEffect(() => {
setTimeout(() => {
setScrollDown(false)
}, 1000)
}, [])
I'm afraid both of the code snippets you mentioned are not correct. useStates expects the value but we're returning nothing. Also, we can't pass second argument to useState. Your best bet would be to utilize useEffect that runs on first render only. There you can place your timeout.
Related
I am using an external 3rd Party that mounts onto the window object when the site loads. This library can be accessed using window.truste. My problem is that truste doesnt exist on the first render of my component and seem to load in a split second after.
useEffect(() => {
const state = window.truste.eu.bindMap.state;
if (validStates.includes(state)) {
setValidState(true);
}
}, []);
If i create a setTimeout function like so then I don't face any issues but this approach seems a little sloppy. I am wondering if there is a way to know that a library is loaded and then try to execute my code.
useEffect(() => {
setTimeout(() => {
const state = window.truste.eu.bindMap.state;
if (validStates.includes(state)) {
setValidState(true);
}
},1000)
}, []);
If the library lets you use it in a dependency array, do that:
useEffect(() => {
if (!validState && window.truste) {
const state = window.truste.eu.bindMap.state;
if (validStates.includes(state)) {
setValidState(true);
}
}
}, [window.truste, validState]);
Otherwise if it's an asynchronous import, wait for it:
useEffect(() => {
(async () => {
const otherLibrary = await import('other-library');
const state = window.truste.eu.bindMap.state;
if (validStates.includes(state)) {
setValidState(true);
}
})();
}, []);
Maybe you can create a polling in useEffect check the window.truste is avaliable.
useEffect(() => {
function check() {
if (!window.truste) {
setTimeout(check, timeout)
} else {
// use window.truste
}
}
check()
}, [])
I am trying to build a cryptocurrency application. But I am having trouble getting populated state values that are passed into the useEffect as parameters to a debouncing/polling function.
The issue is that the debouncing works well, as in it detects the value and calls the api after the 500ms that I specified in debounce. However, the polling portion seems to not have the state values of of transactionType, fromCurrencyAmount, and pair. It seems like after I debounce the input, after 6 seconds the polling will do its thing but the values passed in the params are undefined. Is there anyway I can solve this?
Here is the method that serves two purposes. It has an api to be polled from every 6 seconds, as well as getting debounced input if the user enters an amount inside the input.
function handleInitPoll(baseAndQuote, side, value) {
getSwapPrice(baseAndQuote, side, value || 0)
.then((res) => {
if (!res.price) {
setIsLoading(true);
} else if (res.error) {
setErrorMessage(res.error);
} else if (res.price) {
setIsLoading(false);
setSwapPriceInfo(res);
}
});
}
And here is the useEffect:
useEffect(() => {
handleInitPoll(pair, transactionType, fromCurrencyAmount);
const timer = setInterval(handleInitPoll, 6000, pair, transactionType, fromCurrencyAmount);
return () => {
clearInterval(timer);
};
}
setSelectedCurrencyState({ ...selectedCurrencyState, selectedFromCurrency: 'USDT', selectedToCurrency: 'XLM' });
}, [pair, transactionType, fromCurrencyAmount]);
And here is the debounce declaration:
const debounceOnChange = useCallback(debounce(handleInitPoll, 500, pair, transactionType, fromCurrencyAmount), []);
And here is where the debouncing is being done, which is inside an onChange handler:
const handleAssetAmount = (e) => {
const { value } = e.target;
const formattedAmount = handleAssetAmountFormat(value);
setFromCurrencyAmount(formattedAmount);
validateInputAmount(formattedAmount);
debounceOnChange(pair, transactionType, formattedAmount);
};
Issue
The issue here is that you've closed over stale values in the interval callback.
Solution
One solution is to cache these state values in a React ref such that the current value can be accessed in the polling function.
Example:
const pairRef = React.useRef(pair);
const transactionTypeRef = React.useRef(transactionType);
const fromCurrencyAmountRef = React.useRef(fromCurrencyAmount);
useEffect(() => {
pairRef.current = pair;
}, [pair]);
useEffect(() => {
transactionTypeRef.current = transactionTypeRef;
}, [transactionType]);
useEffect(() => {
fromCurrencyAmountRef.current = fromCurrencyAmount;
}, [fromCurrencyAmount]);
useEffect(() => {
handleInitPoll(pair, transactionType, fromCurrencyAmount);
const timer = setInterval(() => {
handleInitPoll(
pairRef.current,
transactionTypeRef.current,
fromCurrencyAmountRef.current
);
}, 6000);
return () => {
clearInterval(timer);
};
}, [pair, transactionType, fromCurrencyAmount]);
Fundamentally, your code seems to be correct with a few issues:
There is a race condition.
If getSwapPrice is running and the component is updated, it can still
affect the state when setSwapPriceInfo or setLoading are called when
the promise is resolved.
This is particularly bad, because network requests can "overtake" each
other. Thus it can happen that the return value of getSwapPrice
updates the component with the result of an old network request.
This is discussed in this article.
There is this odd call to setSelectedCurrencyState in the useEffect
block. It's not clear what this is supposed to do, but it clearly doesn't
belong there.
However, the underlying application should work fine, I reproduced it with a simpler application here:
import { useEffect, useState } from "react";
function fetchExchangeRateAsync(multiplier) {
return new Promise(resolve => {
setTimeout(() => {
resolve(Math.random() * multiplier);
}, 100);
});
}
function CurrencyExchangeRate() {
const [exchangeRate, setExchangeRate] = useState(null);
const [inputValueString, setInputValueString] = useState("");
const inputValue = Number(inputValueString);
const [multiplier, setMultiplier] = useState(1.0);
let outputValue = null;
if (!isNaN(inputValue) && exchangeRate !== null) {
outputValue = inputValue * exchangeRate;
}
useEffect(() => {
// To avoid race conditions, we must not update the state from an asynchronous operation if
// the component was re-rendered since then.
//
// https://overreacted.io/a-complete-guide-to-useeffect/
let didCancel = false;
// Do not delay the first request.
fetchExchangeRateAsync(multiplier)
.then(newExchangeRate => {
if (!didCancel) {
setExchangeRate(newExchangeRate);
}
});
// Poll exchange rate.
let intervalHandle = setInterval(() => {
fetchExchangeRateAsync(multiplier)
.then(newExchangeRate => {
if (!didCancel) {
setExchangeRate(newExchangeRate);
}
});
}, 500);
return () => {
didCancel = true;
clearInterval(intervalHandle);
};
}, [multiplier]);
return (
<div>
<input value={inputValueString} onChange={event => setInputValueString(event.target.value)} /><br />
<p>With current exchange rate: {outputValue !== null ? outputValue : "(loading)"}</p>
<button onClick={() => setMultiplier(100.0)}>Set Multiplier</button>
</div>
);
}
function App() {
return (
<CurrencyExchangeRate />
);
}
export default App;
This is quite a bit different from what you are doing but it does demonstrate that your code should generally work:
It is possible to trigger the fetch logic by changing the input field (here without debouncing) or in a given interval.
The fetch logic runs immediately when the component is rendered for the first time.
The "Set Multiplier" button can affect the value of multiplier and this information arrives in the setInterval call correctly.
This works because [multiplier] dictates that the effect should be re-run if that variable changes. When this happens, the old interval is first cleared with clearInterval and then re-started with setInterval.
In your case that would be pair, transactionType and fromCurrencyAmount instead of multiplier.
In other words, your issue seems to be outside the code that you provided in the question.
so I have a function that runs every 5 seconds. Inside this function, I check if the state is null, to then set a value to it. The problem is that, every time the function run, it detects the state as null, even tho it is not null.
My code:
const [activeChat, setActiveChat] = useState(null)
const loadChats = async () => {
await api.get('/v1/chat/chats')
.then((res) => {
if (activeChat === null) {
if (res.data.chats.length > 0) {
setActiveChat(res.data.chats[0])
}
}
setChats(res.data.chats)
setLoading(false)
})
.catch((err) => {
setLoading(false)
})
}
useEffect(() => {
loadChats()
let interval = setInterval(() => {
loadChats()
}, 5000);
return () => clearInterval(interval)
}, [])
the activeChat should be only set on the first load, if its not set yet, but it keeps detecting as null every time the function runs. Why does it keep detecting as null?
Obs: As I said, the state is really being set, as expected, so the problem is not with the response or something, i don't know what is happening..
Inside setInterval or setTimeout, the state will NOT be changed even you have changed it inside the setInterval function. Try to create a timer using setInterval and you can see the state does not change inside it.
const [timer, setTimer] = useState(0);
useEffect(() => {
setInterval(() => {
console.log(timer); // remains the same (0) forever
setTimer(timer + 1);
}, 1000);
}, []);
useEffect(() => {
console.log(timer); // this one should only change from 0 to 1, because timer always being set as 0 + 1;
}, [timer]);
You can create a ref for the chat object only for updating, and a state only for the chat display. Use the ref to keep track of the active chat, while using the state for UI display.
I am looking for a better solution to using setTimeout to wait for props using the useEffect() hook. Currently I have working as intended
const sessionCookie = getCookie('_session');
const { verifiedEmail } = props.credentials;
const [loading, setLoading] = useState(true);
useEffect(() => {
const timeoutLoading = setTimeout(() => setLoading(false), 2000);
if (!loading) {
if (sessionCookie) {
if (verifiedEmail) {
window.location.href = 'https://1.example.com';
}
} else {
window.location.href = 'https://2.example.com';
}
}
return () => {
clearTimeout(timeoutLoading);
};
}, [loading]);
whereas props.credentials in it's initial state is {}, awaiting a response from the server to give the value for verifiedEmail and sessionCookie relies on a function getCookie to return whether the cookie exists and the value.
Whilst this works, it's not ideal. One example would be in some user cases, 2s isn't enough time, in others it makes the user wait, when the props are already available.
My question is, how can I create a promise or use async/await to achieve the same affect without the risk of using setTimeout.
NOTE: props.credentials are not available on render and will always return an empty object before containing verifiedEmail. In some cases, the sessionCookie may not be available on render too but will also be available before props.credentials has verifiedEmail. If sessionCookie exists, verifiedEmail will too shortly after.
Ideally I would it best to create a promise around getCookie, then ensure the _session is available before identifying verifiedEmail exists. Any thoughts?
getCookie() is as below
const getCookie = (name: string) => {
const v = document.cookie.match('(^|;) ?' + name + '=([^;]*)(;|$)');
return v ? v[2] : null;
};
Update
Can you add a new prop called hasAuthenticated or something like that. While your request is pending, this can be false. Once the request is complete, set it to true and apply your logic at that time, just like isLoading.
That should remove the need for timers.
Previous
You could try something like this using two useEffects. First one will handle setting your session cookie and the second one will trigger the window change.
I'm not sure how your getCookie function works though.
const [sessionCookie, setSessionCookie] = useState(null);
const [loading, setLoading] = useState(true);
const { verifiedEmail } = props.credentials;
useEffect(() => {
const getSession = async () => {
setSessionCookie(await getCookie('_session'));
setLoading(false);
}
getSession();
}, [setSessionCookie, getCookie, setLoading]);
useEffect(() => {
if (!loading) {
if (sessionCookie) {
if (verifiedEmail) {
window.location.href = 'https://1.example.com';
}
} else {
window.location.href = 'https://2.example.com';
}
}
}, [sessionCookie, verifiedEmail, loading]);
I have an issue with using Lodash + setInterval.
What I want to do:
Retrieve randomly one element of my object every 3 seconds
this is my object:
const [table, setTable]= useState ([]);
so I start with that:
const result = _.sample(table);
console.log(result);
console give => Object { label: "Figue", labelEn: "fig" }
But if a add :
const result = _.sample(table);
console.log(result.label);
console give => TypeError: result is undefined
Beside that I tried to add setInterval and also try with useEffect but or code crash or console give me two numbers every 3 second => 2,6,2,6 ......
Ciao, supposing that table object is correclty filled, you could use lodash and setInterval to get random object of table using useEffect and useRef hooks like:
const interval = useRef(null);
useEffect(() => {
if (!interval.current) {
interval.current = setInterval(() => {
const result = _.sample(table);
console.log(result.label);
}, 3000);
}
}, [table]);
Then to clean setInterval when component will be unmount you could use another useEffect in this way:
useEffect(() => {
return () => {
clearInterval(interval.current);
interval.current = null;
};
}, []);
EDIT
After your explanation, I found the cause of your problem. Before start setInterval you have to wait that table was filled with values (otherwise you get an error).
To solve that it's just necessary to put another condition on first useEffect (table.length > 0), and load data on second useEffect.
So the first useEffect becomes:
const interval = useRef(null);
useEffect(() => {
if (!interval.current && table.length > 0) {
interval.current = setInterval(() => {
const result = _.sample(table);
console.log(result.label);
}, 3000);
}
}, [table]);
And the second one:
useEffect(() => {
jsonbin.get("/b/5f3d58e44d93991036184474/5")
.then(({data}) => setTable(data));
return () => {
clearInterval(interval.current);
interval.current = null;
};
}, []);
Here the codesandbox updated.
The problem is that you access table before it is loaded.
You should either provide an initial value to that allows a successful do all your operations:
// use an array that has at least one element and a label as initial table value
const [table, setTable] = useState([{label: "default label"}]);
useEffect(() => {
jsonbin.get("/b/5f3d58e44d93991036184474/5")
.then(({data}) => setTable(data));
});
const result = _.sample(table);
console.log(result.label);
// ...
Or use an if or something alike to handle multiple scenarios:
// use an empty array as initial table value
const [table, setTable] = useState([]);
useEffect(() => {
jsonbin.get("/b/5f3d58e44d93991036184474/5")
.then(({data}) => setTable(data));
});
const result = _.sample(table);
// account for the initial empty table value by checking the result
// of _.sample which is `undefined` for empty arrays
if (result) {
console.log(result.label);
} else {
console.log("do something else");
}
// ...
If you fetch your data asynchronously you must think about what you want to use while the data is being fetched. The minimal thing to do is tell React to not render the component while the data is missing (being fetch).
const [table, setTable] = useState([]);
useEffect(() => {
jsonbin.get("/b/5f3d58e44d93991036184474/5")
.then(({data}) => setTable(data));
});
const result = _.sample(table);
if (!result) return null; // <- don't render if there is no result
console.log(result.label);
// ...