Automatically change which element of array to display in React - javascript

Using React.
If I had an array like:
[ data:
title: foo1
body: bar1
title: foo2
body: bar2
title: foo3
body: bar3
]
And I wanted to display a single body and title at one time on my page and loop through these elements (have a paragraph that changes its contents routinely).
So far I specify which data I want using this:
let specificBody = this.state.data[x].body; //loop through different elements of array on timer
let specificTitle = this.state.data[x].title;
let specificPosted_By = this.state.data[x].posted_by;
Is there any way to increment the value of x so that I can display a different body and title? Or is there another way of achieving this aim?
All help greatly appreciated.

Some pseudo code, untested:
const [index, setIndex] = useState(0)
useEffect(() => {
const interval = setInterval(() => { /*todo reset at max value*/ setIndex(index+1) }, 10000)
return () => clearInterval(interval)
},[])
and then use index here:
this.state.data[index].body;

This is an example of how you can use setInterval to update the active index periodically.
Your render will then display the text of the active object in the array.
const data = [{ title, body },{ title, body }, { title, body }]
const Component = () => {
const [active, setActive] = useState(0);
useEffect(() => {
//This is how you use setInterval to change the active index every 5 seconds
setInterval(() => {
setActive( previous => {
if (previous === data.length) {
return 0;
} else {
return previous+1;
}
})
}, 5000) //Change every 10 seconds
},[])
return <div>{data[0].title}</div>
}

It looks like you're trying to accomplish two things:
Increment the specific array position
Do this automatically using a timer.
The snippet of code doesn't show what loop logic you're using, but assuming you're going to implement a setInterval inside a useEffect hook, you want to create an independent state variable for 'x' (which marks the array index on your data object).
You can then set a conditional within the useEffect hook, like this:
useEffect(() => {
const intervalId = setInterval(() => {
if(x <= data.length-1) {
setData(data[x])
}
else {
setState({
x = 0;
})
};
x++;
}, 5000);
return () => clearInterval(intervalId);
}, [data, x])

probably a better approach is to divide it into two components, one that hold the data and has the rotation logic and another one that is just presentation
something like this
const Article = (title, body) => {
return <div>...</div>
}
const ArticleCarousel = (data, time) => {
const [index, setIndex] = useState(0)
useEffect(() => {
const interval = setInterval(() => {
setIndex(i => (i + 1) % data.lenght)
}, time)
return () => clearInterval(interval)
})
return <Article {...data[index]} />
}

Related

State values passed in as arguments in useEffect not being read in reactjs

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.

React - State keeps same value even tho it is updated. (Inside function)

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.

Update Boolean Within UseState Hook

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.

Trying to make an API call before using setInterval() in useEffect() hook in React

I'm using a useEffect() hook in React to fetch data on an interval every five seconds. When my app first loads, the initial fetch request takes five seconds because it's in the setInterval() function.
I'm trying to make the API call on page load and then every five seconds after that, make API call on the interval to retrieve new data.
What I've tried that's not working:
useEffect(() => {
await updateData(id, state, setState)
.then(() => {
const interval = setInterval(async () => {
if (id) {
await updateData(id, state, setState); // API call
}
}, 5000);
return () => {
clearInterval(interval);
};
},[lensName, state, setState])
}
What I'm currently doing and would like to improve:
useEffect(() => {
// Make API call, once initial call is made and response is returned make calls on a 5 second interval
const interval = setInterval(async () => {
if (id) {
await updateData(id, state, setState); // API call
}
}, 5000);
return () => {
clearInterval(interval);
};
}, [lensName, state, setState])
}
Any help is greatly appreciated.
the important thing to note here is that your updateData function should return a promise to make await work then your above logic will work perfectly. It will wait until the first API call is not finished before going to the second line.
useEffect(() => {
await updateData(id, state, setState);
const interval = setInterval(async () => {
if (id) {
await updateData(id, state, setState); // API call
}
}, 5000);
//update function would be like:
function updateData(id, state, setState) {
...
return API.get("/url");
}
}, []);
You can use time value outside the useEffect hook. Increment it every 5 seconds and pass it as second argument of useEffect. Whenever this time value gets changed, UseEffect will get triggered and it will run the function inside it.
const [timeInterval, setTimeInterval] = useState(0);
setTimeout(() => {
setTimeInterval(timeInterval + 1);
}, 5000);
useEffect(() => {
await updateData(id, state, setState); // API call
}, [timeInterval]);
I would use two useEffect() calls: one for the 5 second poll, and one that fires only once (with an empty dependency array). Something like this:
// Make API call once
useEffect(() => {
const live = true;
if (id) {
await updateData(id, state, setState, live);
}
return () => { live = false; }
}, []);
// Make API call on a 5 second interval
useEffect(() => {
const live = true;
const interval = setInterval(async () => {
if (id) await updateData(id, state, setState, live);
}, 5000);
return () => {
live = false;
clearInterval(interval);
}
}, [lensName, state, setState]);
Also note that you'll want some sort of flag to let your updateData() function know whether the component is still mounted. If it gets unmounted, you don't just want to cancel the interval, you'll also want to avoid calling setState().
You can add another useEffect without dependency to call api when page is first load. However, it's better to show the logic of updateData that we can know what you want to do.
// Call api when first load
useEffect(() => {
await updateData(id, state, setState);
}, [])
// After, every five seconds to call api
useEffect(() => {
const interval = setInterval(async () => {
if (id) {
await updateData(id, state, setState); // API call
}
}, 5000);
return () => {
clearInterval(interval);
};
}, [lensName, state, setState])
The following solution works fine, modify it as per your needs:
function App() {
const [count, setCount] = useState(0);
const myDummyApi = async () => {
for (let i = 0; i < 10 ** 9; i++) {
const val = i;
}
return { data: "some data" };
};
useEffect(() => {
if (count === 0) { // condition for checking if the API call being made is initial one or not.
myDummyApi().then((data) => {
setCount(count + 1);
});
} else {
const timer = setTimeout(() => {
myDummyApi().then((data) => {
setCount(count + 1);
clearTimeout(timer);
});
}, 5000);
}
}, [count]);
return (
<div className="App">
<span>{`Api Call ${count}`}</span>
</div>
);
}
Full code can be found here in sandbox.
Explanation:
make a initial API call
after the promise is resolved set the state and increase the count, then component renders again
after that, as the count > 0 the API call will be made only after timeout of 5 secs.

React issue with lodash and setInterval

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);
// ...

Categories

Resources