The following member function populates asynchronously a folder_structure object with fake data:
fake(folders_: number, progress_callback_: (progress_: number) => void = (progress_: number) => null): Promise<boolean>
{
return new Promise((resolve, reject) => {
for (let i_ = 0; i_ < folders_; i_++) {
progress_callback_(i_ / folders_ * 100.);
this.add(this.id(), faker.address.city() + i_, random_choice(this.folder_structure_id()));
}
progress_callback_(folders_ / folders_ * 100.);
resolve(true);
})
}
It uses a callback to update the progress within the for loop which is then used to update the state (a progress bar) from within a useEffect() function with an empty dependency array.
let [progress_state_, set_progress_state_] = useState<number>(0);
let [fake_done_, set_fake_done_] = useState<boolean>(false);
useEffect(() =>
{
if (fake_)
folder_structure_.fake(fake_, (progress_) => {
set_progress_state_(progress_)
}).then(value => set_fake_done_(value));
}, [])
if (!fake_ || fake_done_) etc etc
However, the state is not updated (logging the progress in the console seems to work fine). Any ideas as to whether it's possible to update a state from within useEffect?
The reason your useEffect hook isn't working is that it's not called upon progress_state_ state change.
Instead of
useEffect(() =>
{
...
}, [])
Try this instead
useEffect(() =>
{
...
}, [progress_])
Adding progress_ to the dependency array means useEffect will be called every single time progress_ changes. If you leave it as an empty dependency array, then useEffect is only ever called in the very beginning on when the code is mounted to the DOM.
Here's a good explanation on dependency arrays: https://devtrium.com/posts/dependency-arrays
Addressing your final question: Yes, it is possible to update state from within useEffect.
To understand the root of your main issue, I would be curious to see how you are doing your logging. Are you logging from within fake() or from your render() function?
Related
I found this lib https://react-hooks.org/docs/useEffectOnceWhen but I'm confused. Why does it even existed and in what purpose it's used? Isn't I can achieve the same thing using useEffect?
useEffect(() => {
setTimeout(() => {
setLoading(false);
}, 3000); // Countdown for 3 sec
}, [])
The hook is useEffectOnceWhen, not just useEffectOnce. Per the source you linked:
Runs a callback effect atmost one time when a condition becomes true
Here's the source code of that hook:
/**
* useEffectOnceWhen hook
*
* It fires a callback once when a condition is true or become true.
* Fires the callback at most one time.
*
* #param callback The callback to fire
* #param when The condition which needs to be true
*/
function useEffectOnceWhen(callback: () => void, when: boolean = true): void {
const hasRunOnceRef = useRef(false);
const callbackRef = useRef(callback);
useEffect(() => {
callbackRef.current = callback;
});
useEffect(() => {
if (when && !hasRunOnceRef.current) {
callbackRef.current();
hasRunOnceRef.current = true;
}
}, [when]);
}
As you can see, it does something more advanced than just being called once at the beginning of the hook: it gets called once the condition passed in becomes true for the first time.
You can do the same thing with useEffect:
"If you want to run an effect and clean it up only once (on mount and unmount), you can pass an empty array ([]) as a second argument"
Docs
You can also supply a list of dependencies that trigger the effect running again if they change.
useEffect( () => { doSomething() } , [ifThisChangesRunFuncAgain, orThis, ...] )
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)
}
I have a component which takes a deep object as argument. I want to be able to dynamically alter my component based on the property that I pass - But for some reason my useEffect() loop doesn't run the 2nd time that I pass an object, which naturally is due to the fact that it is a deep object which useEffect() doesn't "recognize"
My code is as follows:
<MyComponent groups={sourceArray}/>
function MyComponent({ groups }) {
useEffect(() => {
//Do something
}, [groups])
For clarification, my data is an array of objects.
I did try several things to get this to work - but for some reason, I just cant get my effect loop to trigger:
First alternative solution which doesn't work (useRef)
useEffect(() => {
if (prevGroups && !isEqual(prevGroups, groups)) {
// do something
}
}, [groups])
const usePrevious = (value) => {
const ref = useRef();
useEffect(() => {
ref.current = value;
});
return ref.current;
};
const prevGroups = usePrevious(groups);
2nd solution which also fails (External library)
I tried using the following library, - Also without any luck.
Conclusively, I'm unsure what's going on here and how I would get my useEffect loop to run on update.
The easy solution would be to remove the dependency array - But that just makes the useEffect loop run infinitely
Update
object has the following structure:
[{"name": "123", "id": "1", "children": [{...}]}]
Also, I'm passing the value as a useState(), i.e.:
const funcToChangeObj = () => {
//logic to change sourceArray
setSourceArray(changedArray)
}
setSourceArray(changedArray) -> setSourceArray(changedArray.slice())
I am trying to sort all objects that match the regex into an array.
This does not seem to work with the spread operator and useState, is there any way I can do that?
The result I am getting now is the samples thing only gives me the last object that matches it and nothing else.
The desired effect I want is all the samples that match get pushed into the samples state.
const [accessories, setAccessories] = useState([]);
const [paints, setPaints] = useState([]);
const [samples, setSamples] = useState([]);
// Load order into state
useEffect(() => {
loadUser();
getOrderById(match.params.orderId);
}, []);
// Load order into state
useEffect(() => {
if (!loading) {
console.log(order.line_items);
for (let i = 0; i < order.line_items.length; i++) {
if (order.line_items[i].sku.match(/^(TAC|T.BU.AC)/)) {
console.log('SKU: ', order.line_items[i].sku);
//#ts-ignore
setAccessories([...accessories, order.line_items[i]]);
console.log(accessories);
}
if (order.line_items[i].sku.startsWith('TBA') || order.line_items[i].sku.match(/^TCR(?!0000)/)
|| order.line_items[i].sku.match(/^TCR0000/)) {
//#ts-ignore
setPaints([...paints, order.line_items[i]]);
}
if (order.line_items[i].sku.match(/^TCR\d+P?\d+SAMP/)) {
console.log(samples);
console.log(order.line_items[i]);
//#ts-ignore
setSamples([...samples, ...[order.line_items[i]]]);
}
}
}
}, [loading]);
Well there are few mistakes you're doing here.
Mistake 1:
Calling the same setStates way too many times inside a single useEffect block using a for loop, this might greatly affect React's performance. Again, this is clearly a violation of Rules of Hooks, Only Call Hooks at the Top Level
Only Call Hooks at the Top Level
Don’t call Hooks inside loops, conditions, or nested functions.
Mistake 2:
Though this is not as serious as the previous ones, it's still a mistake. Not using better solutions, Use inbuilt JavaScript methods like filter instead of writing your own for loop
useEffect(() => {
let _accessories;
let _paints;
let _samples;
if (!loading) {
_accessories = order.line_items.filter(({ sku }) => sku.match(/^(TAC|T.BU.AC)/))
_paints = order.line_items.filter(({ sku }) => sku.startsWith('TBA') || sku.match(/^TCR(?!0000)|^TCR0000/))
_samples = order.line_items.filter(({ sku }) => sku.match(/^TCR\d+P?\d+SAMP/))
// Never use setState inside a for loop
// of useEffects
// Also avoid calling same setState multiple times
// use callback setState if you want to access
// previous state, but it ain't a compulsory like
// for class components
setAccessories(s => [ ...s, ..._accessories ])
setPaints(s => [ ...s, ..._paints ])
setSamples(s => [ ...s, ..._samples ])
}
// return in useEffect has different role
// than normal functions
}, [loading])
Spread the results of calling .filter into the calls:
useEffect(() => {
if (loading) {
return;
}
const items = order.line_items;
setAccessories([
...accessories,
items.filter(({ sku }) => sku.match(/^(TAC|T.BU.AC)/))
]);
setPaints([
...paints,
items.filter(({ sku }) => sku.startsWith('TBA') || sku.match(/^TCR(?!0000)|^TCR0000/))
]);
setSamples([
...samples,
items.filter(item => item.sku.match(/^TCR\d+P?\d+SAMP/))
]);
}, [loading]);
I'm having trouble with setState when using the package: reactn
When I replace these lines (1) with those lines (2), the code works. The (2) is a workaround, deals with asynchronous setState, but I want to understand why (1) doesn't work.
As I know, I can pass a callback function to setSomeState in React Hooks:
If the new state is computed using the previous state, you can pass a function to setState
This is also another usage of useGlobal from the document of reactn, which also uses a callback function as an argument for setGlobal. Why their example works but mine doesn't?
Full code: https://snack.expo.io/#loia5tqd001/d26e8f
Snippets:
listSymbols = [ "USD", "EUR", ... ]
usdRates = {} // expect to be usdRates = { USD: 1, EUR: 0.9 ... }
// getExchangeRate is in utils/utils.js
// => The code doesn't work
for (const symbol of listSymbols) {
getExchangeRate("USD", symbol).then(exchangeRate => {
setUsdRates(oldUsdRates => ({
...oldUsdRates,
[symbol]: exchangeRate
}))
.then(() => console.log("Call api getting exchange rate for " + symbol, usdRates) )
})
}
// => The code works as expected
for (const symbol of listSymbols) {
getExchangeRate("USD", symbol).then(exchangeRate => {
usdRates[symbol] = exchangeRate
console.log("Call api got exchange rate for " + symbol, usdRates)
})
}
setUsdRates(usdRates)
Based on the source of reactn it doesn't appear that the updater function style is supported when using the useGlobal('propertyName') version of the hook.
Here is the definition of the property setter: https://github.com/CharlesStover/reactn/blob/master/src/use-global.ts#L95
You can see it creates a newGlobalState object and passes it to setGlobal.
Then setGlobal calls the set on the global state manager here: https://github.com/CharlesStover/reactn/blob/master/src/global-state-manager.ts#L302
Since newGlobalState from the property setter is always an object, the updater version is never used.
You could achieve what you want by passing nothing to useGlobal and dealing with the entire state object instead, as per the example in the docs that you linked:
const [global, setGlobal] = useGlobal();
...
getExchangeRate("USD", symbol).then(exchangeRate => {
setGlobal(oldGlobal => ({
...oldGlobal,
usdRates: {
...oldGlobal.usdRates,
[symbol]: exchangeRate,
},
}))
.then(() => console.log("Call api getting exchange rate for " + symbol, usdRates) )
})
}
Also I'm not sure your other example is 100% correct - you're not waiting to call setUsdRates until all of the async getExchangeRate calls are complete.
Okay I will try to clear some things here
as you said
usdRates = {} // expect to be usdRates = { USD: 1, EUR: 0.9 ... }
So it's supposed to be an object
const [usdRates, setUsdRates] = useGlobal({})
then do
useEffect(() => {
for (const symbol of listSymbols) {
getExchangeRate("USD", symbol).then(exchangeRate => {
setUsdRates(oldUsdRates => ({
...oldUsdRates,
[symbol]: exchangeRate
}))
.then(() => console.log("Call api getting exchange rate for " + symbol, usdRates)
)
})
}
}, [])
Hope it helps