How to replace one component to another in React native - reactjs

I have three component in screen with same size but different static data.
I want to replace the every 5 seconds on the screen in the same area(display position is fix). For ex. I have <FeedbackComp1>, <FeedbackComp2> and <FeedbackComp3>. So, I want display and replace the component every 5 seconds.
Display: first time render the screen display the <FeedbackComp1> and then replace the place <FeedbackComp2> and then <FeedbackComp3> and then <FeedbackComp1> so on.
If possible this functionality in array map() function so I also ready for that.
Thank you.

Something like this should do the trick.
import { useEffect, useState } from 'react'
// placeholders for your actual components.
const FeedbackComp1 = () => <></>
const FeedbackComp2 = () => <></>
const FeedbackComp3 = () => <></>
const MyComponentSwitcher = () => {
const [index, setIndex] = useState(0)
const components = [<FeedbackComp1/>, <FeedbackComp2/>, <FeedbackComp3/>]
useEffect(() => {
const interval = setInterval(() => {
setIndex(currentIndex => (currentIndex + 1) % components.length)
}, 5000)
return () => clearInterval(interval)
}, [])
return components[index]
}
Short explanation:
The index state-variable holds the index we're currently at.
The components array holds the components you want to switch between.
The useEffect initializes an interval of 5000 ms, so that every 5 seconds, it will set the index to one more than it currently is, using the remainder operator % to ensure we never have an index outside the array.
Note that the setIndex doesn't take in a new value, but rather a callback. This allows us to automatically set the value to one higher than before, without having to reference the value when the component is initialized. React reference.
The return statement simply returns the current component.

Related

How to call a function which returns a number in Drawer item using React Native?

I am trying to show the number of each item in Drawer Item in react native. for this purpose I created a function which returns the number of items I want. But it does not give me what I want. How can I call this function in Drawer Item title?
function:
const countItems = async () => {
const storedArray = await AsyncStorage.getItem('fav_data').then(favs => {
const count = JSON.parse(favs).length;
return count;
})
// console.log(storedArray)
return storedArray;
}
and here I want to call it:
<DrawerItem title={'test ' + countItems()} accessoryLeft={HeartIcon}
onPress={() => navigation.navigate('Favorites')}/>
You're not awaiting the result of countItems, so it will return undefined at render time. Since the number isn't stored in state, changes to the number won't trigger a re-render of the screen.
Here's an example of the most common way to solve this problem: with state and effect hooks.
const [count, setCount] = useState();
useEffect(() => {
AsyncStorage.getItem('fav_data').then(favs => {
const count = JSON.parse(favs).length;
setCount(count);
})
}, []);
...
<DrawerItem
title={count ?? 'Loading'}
accessoryLeft={HeartIcon}
onPress={() => navigation.navigate('Favorites')}
/>
The useEffect hook has an empty dependency array (the 2nd argument), so it will only run once, on the mount of the component.
If you want to read more about the state and effect hooks, sections 3 and 4 of the following React doc have good explanations: https://reactjs.org/docs/hooks-intro.html

How to update the state with the latest fetched item with an interval callback function inside useEffect?

I'm quite new to the React-TS world and I have recently been playing with useState and useEffect hooks only basically.
I have the following functional component inside which I'd like to fetch N items the first time and then start a periodic function that fetches the last item from the response data, updating the current state.
const fetcher = async (url: string) => await axios.get(url).then((res: AxiosResponse) => res.data);
type AirflowData = {
value: number; // perc values from 0 to 1
timestamp: number; // UTC time
};
const ActionDetector: React.FC = () => {
const [alerts, setAlerts] = useState<AirflowData[]>([]);
useEffect(() => {
// Fetch the latest N alerts first
getAlerts(100);
// Then start fetching the last alert every N milliseconds
const interval = setInterval(() => getLatestAlert(), 1000);
// Clear interval
return () => {
clearInterval(interval);
};
}, []);
/**
* Return the alert data after fetching it.
* #param numAlerts number of the last N alerts to return
*/
const getAlerts = async (numAlerts: number) => {
const fetchedAlerts: AirflowData[] = await fetcher("http://localhost:9500/alerts");
setAlerts(fetchedAlerts.slice(-numAlerts));
};
/**
* Return the latest alert data available.
*/
const getLatestAlert = async () => {
const fetchedAlerts: AirflowData[] = await fetcher("http://localhost:9500/alerts");
const latestFetchedAlert = fetchedAlerts.slice(-1)[0];
const latestAlert = alerts.slice(-1)[0];
if (latestFetchedAlert && latestAlert && latestFetchedAlert.timestamp !== latestAlert.timestamp) {
// Append the alert only if different from the previous one
setAlerts([...alerts, latestFetchedAlert]);
}
};
console.log(alerts);
return <></>
}
export default ActionDetector
The problem with this approach is that latestAlert is always undefined and that is due, if I understood how React works under the hood correctly, to the initial state change re-rendering trigger. After getAlerts() is called and fires setAlerts(...), the component starts the re-rendering and so, since getLatestAlert() is called inside the useEffect only the first time (the first render), it always read alerts as the initialized empty array.
I don't know if this is the correct reason behind this, but how can I achieve what I'm trying to do the right way?
The fundamental issue is that when updating state based on existing state, you need to be sure you have the latest state information. Your getLatestAlerts function closes over the alerts constant that was in scope when it was created, so it only ever uses that version of the constant (not the updated one from a subsequent render). Your useEffect setInterval callback closes over the getLatestAlerts function that was in scope when it was created, and only ever uses that version of the function.
To be sure you have the latest state, use the callback version of the state setter instead of the constant:
const getLatestAlert = async () => {
const fetchedAlerts: AirflowData[] = await fetcher("http://localhost:9500/alerts");
const latestFetchedAlert = fetchedAlerts.slice(-1)[0];
if (latestFetchedAlert) {
setAlerts(alerts => {
const latestAlert = alerts.slice(-1)[0];
if (latestFetchedAlert && latestAlert && latestFetchedAlert.timestamp !== latestAlert.timestamp) {
// Append the alert only if different from the previous one
alerts = [...alerts, latestFetchedAlert];
}
return alerts;
});
}
};
Purely as a side note, I wouldn't use the idiom you seem to be using to get the last item from an array, array.slice(-1)[0]. Instead, I'd either use array[array.length - 1], or use the at method which just achieved Stage 4 and will be in this year's spec (it's easily polyfilled for older environments).

State not update when using setter and setInterval in useEffect

I have the following code, which I expect it to print an extra 10 every 2 seconds on the screen. However, it only prints 10 once. console.log(digits) gave me the correct array of 10s. How can I add a 10 to the array every 2 seconds and print the updated array on the screen each time the 10 is added?
Code sandbox here: https://codesandbox.io/s/silly-https-zk58p?file=/src/App.js
import { useEffect, useState } from "react";
let data = [];
export default function App() {
const [digits, setDigits] = useState([]);
useEffect(() => {
let timer = setInterval(() => {
data.push(10);
setDigits(data);
console.log(digits);
}, 2000);
return () => clearInterval(timer);
}, [digits]);
return <div className="App">{digits}</div>;
}
The issue is with setDigits(data). With arrays, you should be executing setDigits([...data]).
Another way would be doing this:
let timer = setInterval(() => {
setDigits([...digits, 10]);
}, 2000);
Whenever dealing with objects, you should treat them as immutables. What happened here is you modifying an array and puhsing the SAME array into a state. While the value might be different, the array is actually the same hence it does not update the render. Whenever doing [...data] it creates a new array with the same values hence trigger the update.
useEffect picks up the new value change hence why it fires again(cant be observed by console.log()), but this does not trigger re-render of the component.
In your code, you are mutating the same array data by pushing 10 after each 2 seconds. As a result, the useEffect is not executing in the subsequent renders. So either you spread data array as in the following code snippet or simply get rid of the data variable and rely on digits array as Lith suggested.
export default function App() {
const [digits, setDigits] = useState([]);
useEffect(() => {
let timer = setInterval(() => {
data = [...data, 10];
setDigits(data);
console.log(digits);
}, 2000);
return () => clearInterval(timer);
}, [digits]);
return <div className="App">{digits}</div>;
}

React state not updating inside setInterval

I'm trying to learn React with some simple projects and can't seem to get my head around the following code, so would appreciate an explanation.
This snippet from a simple countdown function works fine; however, when I console.log, the setTime appears to correctly update the value of 'seconds', but when I console.log(time) immediately after it gives me the original value of 3. Why is this?
Bonus question - when the function startCountdown is called there is a delay in the correct time values appearing in my JSX, which I assume is down to the variable 'seconds' being populated and the start of the setInterval function, so I don't get a smooth and accurate start to the countdown. Is there a way around this?
const [ time, setTime ] = useState(3);
const [ clockActive, setClockActive ] = useState(false);
function startCountdown() {
let seconds = time * 60;
setClockActive(true);
let interval = setInterval(() => {
setTime(seconds--);
console.log(seconds); // Returns 179
console.log(time); // Returns 3
if(seconds < 0 ) {
clearInterval(interval);
}
}, 1000)
};
Update:
The reason you are not seeing the correct value in your function is the way that setState happens(setTime). When you call setState, it batches the calls and performs them when it wants to in the background. So you cannot call setState then immediately expect to be able to use its value inside of the function.
You can Take the console.log out of the function and put it in the render method and you will see the correct value.
Or you can try useEffect like this.
//This means that anytime you use setTime and the component is updated, print the current value of time. Only do this when time changes.
useEffect(()=>{
console.log(time);
},[time]);
Every time you setState you are rerendering the component which causes a havoc on state. So every second inside of your setInterval, you are re-rendering the component and starting it all over again ontop of what you already having running. To fix this, you need to use useEffect and pass in the state variables that you are using. I did an example for you here:
https://codesandbox.io/s/jolly-keller-qfwmx?file=/src/clock.js
import React, { useState, useEffect } from "react";
const Clock = (props) => {
const [time, setTime] = useState(3);
const [clockActive, setClockActive] = useState(false);
useEffect(() => {
let seconds = 60;
setClockActive(true);
const interval = setInterval(() => {
setTime((time) => time - 1);
}, 1000);
if (time <= 0) {
setClockActive(false);
clearInterval(interval);
}
return () => {
setClockActive(false);
clearInterval(interval);
};
}, [time, clockActive]);
return (
<>
{`Clock is currently ${clockActive === true ? "Active" : "Not Active"}`}
<br />
{`Time is ${time}`}
</>
);
};
export default Clock;

Change text at every time interval - React

I am trying to change a text (it begins automatically when the screen appears) at every time interval in react, but the problem is that, the time given isn't respected, and the text is changing at a random time interval.
This is a part of my code:
const names = [
'tony', 'elias', 'fadi'
]
const [newName, setnewName] = useState(0);
useEffect(() => {
for (const [index, value] of names.entries()) {
setTimeout(() => { shuffle(value) }, 5000);
}
})
const shuffle = (value) => {
setnewName(value);
}
And thank you!
Couple things here, but the main issue is the use of setTimeout in a useEffect call with no dependency array. So you're calling shuffle 5000ms after each render, which is why the updates seem to occur at random times. Additionally, the way shuffle is called looks like it will pose some issues.
You should modify your code so that the shuffle function selects a random element from the names array on its own and only call shuffle one time (you might also consider renaming shuffle to something like selectRandomName). Then change setTimeout to setInterval and only call that on mount (instead of on each render).
Here's a full example:
const names = [
'tony', 'elias', 'fadi'
]
function MyComponent() {
const [newName, setnewName] = useState("");
const shuffle = useCallback(() => {
const index = Math.floor(Math.random() * names.length);
setnewName(names[index]);
}, []);
useEffect(() => {
const intervalID = setInterval(shuffle, 5000);
return () => clearInterval(intervalID);
}, [shuffle])
return(
<Text>name:{newName}</Text>
)
}
Note the use of useCallback here is to prevent useEffect from running on each render while also preventing linter warnings from showing up.

Resources