I'm retrieving data from cloud firestore as an array of objects and I pass the object's values as props to another component:
renderTips() {
firebase.firestore().collection('pendingtips').get()
.then(doc => {
doc.forEach(tip => {
const tipData = tip.data();//array's object
console.log(tipData.tip); //prints tip as expected
console.log(tipData.name); //prints name as expected
return <PendingTip key={tipData.tip} name={tipData.name} tip={tipData.tip} />; //doesn't returning enything
});
})
.catch(() => Alert.alert('error'));
}
render() {
return (
<View style={styles.containerStyle}>
<ScrollView style={styles.tipsContainerStyle}>
{this.renderTips()}
</ScrollView>
</View>
);
}
The array of objects looks like this:
{ name: 'Danny', tip: 'Be careful when crossing the road' },
{ name: 'Alex', tip: 'Drink water' }
The expectation is that in my ScrollView I will have a list of "tips". instead, I get nothing back as if the values are not being passed to the component.
thanks in advance.
RenderTips returns a promise which means it won't return anything at first render but only when the promise resolves. You need setState in renderTips to tell react to re-render your component when data comes. Make a seperate state array object for pendingTips then add the pendingTips component to that array and call setState
this.state = { pendingTips: [] }
componentDidMount() {
let pendingTips = [] // declare an array
firebase.firestore().collection('pendingtips').get()
.then(doc => {
doc.forEach(tip => {
const tipData = tip.data();//array's object
pendingTips.push(<PendingTip key={tipData.tip} name={tipData.name} tip={tipData.tip} />); // push items in the array
});
this.setState({pendingTips})
})
.catch(() => Alert.alert('error'));
}
render() {
return (
<View style={styles.containerStyle}>
<ScrollView style={styles.tipsContainerStyle}>
{this.state.pendingTips.map(tips => tips)}
</ScrollView>
</View>
);
}
You can solve this issue by setting doc as a state property and by moving the function for getting data into either some lifecycle method or effect hook.
You can try something like this:
componentDidMount () {
firebase.firestore().collection('pendingtips').get()
.then(doc => {
this.setState({doc})
})
}
renderTips() {
const {doc} = this.state
return doc ? doc.map(tip => {
const tipData = tip.data();
return <PendingTip
key={tipData.tip}
name={tipData.name} tip={tipData.tip} />;
}) : null
}
render() {
return (
<View style={styles.containerStyle}>
<ScrollView style={styles.tipsContainerStyle}>
{this.renderTips()}
</ScrollView>
</View>
);
}
Related
I have been trying to display map an array after resolving its promise. Unfortunately, it is not being rendered. Here is the code:
<View>
{array.map((item) => {
promiseFunction(item).then((res) => {
return (
<FunctionalComponent
prop={res.prop}
/>
);
});
})}
</View>
You have to separate the async function from rendering. And you have to do the array.map properly. The list of FunctionalComponent props must be a state.
const [propsArray, setPropsArray] = useState([])
useEffect(() => { // or any other function, that will feed data
array.map(item => {
promiseFunction(item).then(res => {
setPropsArray(p => [...p, res])
}
},
[])
return (<View>
{propsArray.map(c => (<FunctionalComponent prop={c.prop} />) }
</View>)
Array.prototype.map() won't wait for Promise to resolve, so data will never be rendered. You should try classic for ... of.
const Foo = ({}) => {
const [components, setComponents] = useState([])
useEffect(() => {
(async () => {
for (const item of array) {
setComponents([...conponents, <FunctionalComponent prop={(await promiseFunction(item)).prop}/>])
}
})()
}
return (
<View>
{components}
</View>
)
}
You can't do that directly as you are trying to since React will try to render an array of promises and when they have resolved it's too late the Component has already rendered. Previous answers showd you how to set to state the results once they come and render only after the promises are fulfilled. But with React#18 you could jump that step and render the promises almost directly.
If you are using Suspense, there is a way to handle this kind of scenarios, because Suspended components are able to consume promises directly and to render a fallback until they resolve:
const arr = [1, 2, 3, 4, 5];
export default function App() {
return (
<Suspense fallback={<p>Loading.....</p>}>
<AsyncComponent />
</Suspense>
);
}
export const AsyncComponent = () => {
// THIS IS AN ARRAY OF PROMISES
const data = arr.map((el, i) => {
const d = useGetData(`https://jsonplaceholder.typicode.com/todos/${i + 1}`);
console.log(d);
return (
<h5>
{i + 1}:{d?.title}
</h5>
);
});
// HERE YOU RENDER THE PROMISES DIRECTLY
return <div>{data}</div>;
};
A demo that you can play with, HERE.
I was successfully able to send data from Java to React-Native using a callback that invokes an array. I can display said data in the console, however I want to be able to display it inside the react-native component itself.
This is the Java method that is supposed to get all the IP addresses connected to my wifi (Thanks to JavaPF from javaprogrammingforums.com for the code)
#ReactMethod
public void displayConnectedDevices(Callback errorCallback, Callback successCallback) throws IOException {
WritableArray array = new WritableNativeArray();
try{
InetAddress localhost= InetAddress.getLocalHost();
byte[] ip = localhost.getAddress();
for(int i = 1; i <= 254; i++)
{
ip[3] = (byte)i;
InetAddress address = InetAddress.getByAddress(ip);
if (address.isReachable(1000))
{
System.out.println(address + "can be pinged");
array.pushString(address.toString());
}
else if(!address.getHostAddress().equals(address.getHostName()))
{
System.out.println(address + "this machine is known in a DNS lookup");
}
else
{
System.out.println(address + "host name could not be resolved");
}
}//end of for loop
successCallback.invoke(array);
} catch (IllegalViewOperationException e) {
errorCallback.invoke((e.getMessage()));
}
}
This is the React-Native method where I want to display the array:
//Importing native java module
import {NativeModules} from 'react-native';
var ConnectedDevicesList = NativeModules.ConnectedDevices;
let LanScanner = () => {
const [arr, setArray] = useState([])
displayActiveConnections = async () => {
ConnectedDevicesList.displayConnectedDevices( (array) => { setArray(array)}, (msg) => {console.log(msg)} );
}
return (
<ScrollView>
<View>
<TouchableOpacity onPress={ this.displayActiveConnections }>
arr.map((item,index)=><Text key= {"conlist"}>{item}</Text>)
</TouchableOpacity>
</View>
</ScrollView>
);
};
export default LanScanner
All the guides I found point to rendering this data on the console, but not in the actual component. What should I do if I want to display this data?
Thank you in advance.
You can use useState. useState is a react hook and on getting the value from the api set the value there. Looks like the api response is an array so you can use map and display the Text
export const SomeFunction{
const [arr, setArray] = useState([])
displayActiveConnections = async () => {
ConnectedDevicesList.displayConnectedDevices( (array) => {
setArray(array)
}, (msg) => {console.log(msg)} );
}
render() {
return (
<ScrollView>
<View>
<TouchableOpacity onPress={ this.displayActiveConnections }>
arr.map((item,index)=><Text key= {someId}>{item}</Text>)
</TouchableOpacity>
</View>
</ScrollView>
);
}
}
}
So I finally got it to work with useState after some trial and error. This however, gives me an error that "each child should have a unique key". I have yet to fix that, but at least now I can see the data in the component (which in this case is a list of IP Addresses). Thanks to brk for his help!
import {NativeModules, View, TouchableOpacity, Text, ScrollView, SafeAreaView} from 'react-native';
import LanScannerStyle from '../Styles/LanScannerStyles';
//Declaring new instance of Java Module
var ConnectedDevices = NativeModules.ConnectedDevices;
export const LanScanner = () => {
const [arr, setArray] = useState([]);
displayConnectionListFromJava = () => {
//Getting the data from native module, and passing it to the arr variable with useState
try{
ConnectedDevices.displayConnectedDevices( (arrayResponse) => {setArray(arrayResponse), console.log(arrayResponse)} );
}
catch(error)
{
console.log(error);
}
//Map the arr variable and render it as text
return(
<View>
{
arr.map((items, index) =>
<View>
<Text>{items}</Text>
</View>)
}
</View>
);
};
return(
<ScrollView>
<View>
{this.displayConnectionListFromJava()}
</View>
</ScrollView>
);
}
I am getting a response from an API:
{
"data": {
// other stuff
"time_breakup": {
"break_timings": [
{
"break_in_time": "2021-11-18T05:32:35.747Z",
"break_out_time": "2021-11-18T05:32:47.871Z"
},
{
"break_in_time": "2021-11-18T06:21:35.740Z",
"break_out_time": "2021-11-18T06:21:39.909Z"
}
],
},
},
"success": true
}
I am using the below function to get this response:
const [shift, setShift]: any = useState();
const getShiftDetails = useCallback(() => {
ApiFunctions.get('shift/' + ID)
.then(async resp => {
if (resp) {
setShift(resp.data); // saving the response in state
// some work
} else {
Alert.alert('Error', resp);
}
})
.catch((err: any) => {
console.log(err);
});
}, []);
useEffect(() => {
getShiftDetails();
}, [getShiftDetails, ID]);
So, I have saved the response in a state shift. Now I want to map this state to display the time on screen:
<View>
{shift.time_breakup.break_timings.map((item: any, index: any) => {
console.log(item.break_in_time),
<>
<View>
<Text>{item.break_in_time}</Text>
<Text>{item.break_out_time}</Text>
</View>
</>;
})}
</View>
However, I am not able to see <Text>{item.break_in_time}</Text> on screen; and also, in the console, I am getting an infinite loop of time:
console.log:
2021-11-18T05:32:35.747Z
2021-11-18T06:21:35.740Z
2021-11-18T05:32:35.747Z
2021-11-18T06:21:35.740Z
2021-11-18T05:32:35.747Z
2021-11-18T06:21:35.740Z
2021-11-18T05:32:35.747Z
...
I don't know what I am doing wrong.
Try adding the ID inside the getShiftDetails useCallback dependencies array.
const getShiftDetails = useCallback(() => {...}, [ID]) I believe this is what is causing the infinite loop
Put the console.log before returning the view from the map function:
<View>
{shift.time_breakup.break_timings.map((item: any, index: any) => {
console.log(item.break_in_time);
return (
<View>
<Text>{item.break_in_time}</Text>
<Text>{item.break_out_time}</Text>
</View>
);
})}
</View>
You get infinite loop because on each render your function getShiftDetails gets redefined, React creates a shallow object of it on each render cycle, you can use useCallback to memoize it and Declare ID as dependency array.
I am trying to create an image grid like the following on React Native.
I have managed to extract the data from https://pokeapi.co/ using Axios. My code is as the following so far but doesnt seem to work. The code below retrieves data from the API and I have set that data to setPokemon (How to I access this data) I have tried to assign that data to {data} below to be used inside the flatlist but its not working. It doesnt seem to assign the data at all.
export default function App() {
const [pokemons, setPokemon] = useState([])
//Fetching Pokemon from online database
async function fetchPokemon() {
try {
const { data } = await axios.get('https://pokeapi.co/api/v2/pokemon?limit=50')
setPokemon(data.results) // ASSIGN DATA TO setPokemon
}
}
//Hook to fetch Pokemon upon component mount
useEffect(() => {
fetchPokemon()
}, [])
const renderPokemon = (item, index) => {
return <Text>{item.name}</Text>
}
const {data} = setPokemon // ALL POKEMON SHOULD BE INSIDE THIS
return (
<SafeAreaView>
<FlatList
style={styles.container}
data={data} // ALL POKEMON SHOULD BE INSIDE THIS
renderItem={renderPokemon}
keyExtractor={pokemons => `key-${pokemons.name}`}
>
</FlatList>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1
},
});
Any tips on this?
You are trying to access data from the state setter function. After calling setPokemon(5), pokemons will be 5.
const {data} = pokemons expects your data to be an object, but you've initialized it as a list, and it looks like you're trying to populate it as a list. Do you mean to write const data = pokemons to simply rename it rather than destructuring it?
Assuming that data.results is a list of stuff, here's what the working component will look like:
function App() {
const [pokemons, setPokemon] = useState([]);
async function fetchPokemon() {
try {
const { data } = await axios.get('https://pokeapi.co/api/v2/pokemon?limit=50')
setPokemon(data.results)
}
}
useEffect(fetchPokemon, [])
const renderPokemon = (item, index) => {
return <Text>{item.name}</Text>
};
return (
<SafeAreaView>
<FlatList
style={styles.container}
data={pokemons} // ALL POKEMON SHOULD BE INSIDE THIS
renderItem={renderPokemon}
keyExtractor={pokemons => `key-${pokemons.name}`}
>
</FlatList>
</SafeAreaView>
);
};
Edit: it seems like there is another error related to object destructuring. If you look at the FlatList docs the renderItem requires a function with this signature: (o: {item: T, index: number}) => Element. Simply update your renderPokemon function.
I want to make the FlatList to Load more when scrolling to end also when I go back to top I need the items ready
I tried to use startAfter() method but it shows like it is not working with real time only with Firestore so the query not retrieve data when scrolling to bottom also when I use startAt() with the last key it shows error said: you should use a unique key.
see the code below :
componentDidMount = () => {
this.retrieveData();
}
retrieveData = async () => {
try {
let services = await firebase.database().ref('services').orderByKey().limitToLast(5)
services.on('value', snapshot => {
let arrayOfKeys = Object.values(snapshot.val())
referenceToOldestKey = arrayOfKeys[arrayOfKeys.length - 1].serviceKey;
this.setState({
dataSource: arrayOfKeys,
lastVisible: referenceToOldestKey,
});
});
}
retrieveMore = async () => {
try {
this.setState({
refreshing: true,
});
let services = await firebase.database().ref('services').orderByKey().endAt(this.state.lastVisible).limitToLast(6)
services.on('value', snapshot => {
let arrayOfKeys = Object.values(snapshot.val())
.sort()
.reverse()
.slice(1);
referenceToOldestKey = arrayOfKeys[arrayOfKeys.length - 1].serviceKey;
this.setState({
dataSource: arrayOfKeys,
lastVisible: referenceToOldestKey,
refreshing: false,
});
});
}
render() {
return (
<View style={styles.container}>
<FlatList
data={this.state.dataSource}
renderItem={({ item }) => (
<View style={styles.itemContainer}>
<Text>{item.serviceName}</Text>
</View>
)}
keyExtractor={(item) => item.serviceKey.toString()}
ListFooterComponent={this.renderFooter}
onEndReached={this.retrieveMore}
onEndReachedThreshold={0}
refreshing={this.state.refreshing}
/>
)
}