Reputation: 338
I am trying to learn React Native and Google's firebase. I have been able to successfully create users and I have a function that successfully gets all users and logs their username to the console. However, I am having a lot of trouble finding a way to display the list of user names to the screen.
I think that a lof of my trouble stems from the fact that I am using a functional component instead of a class component which is what I see similar questions using and so far I've had kind of a hard time translating between the two.
I have this in my firebase methods file:
export const getAllUsers = () => firebase.firestore().collection('users').get();
And I have this screen that currently calls a function that gets all usernames and logs them to the console. What I want, is to be able to access the username (and maybe even the Role and age) and create something like Text></Text elements with them. So something like Text>users.userName>/Text>.
import React, {useState} from 'react'
import { StyleSheet, Text, View, TouchableOpacity, TextInput, Button, Picker, ScrollView} from 'react-native'
import { NavigationContainer, useNavigation, useRoute } from '@react-navigation/native';
import * as firebase from '../../firebase';
const CharacterSelectScreen = ({navigation, route}) => {
React.useEffect(()=>{
console.log('Select Screen loaded.')
userFetch();
},[])
const userFetch = async () => {
try{
const users = await firebase.getAllUsers();
// users is a QuerySnapshot
if (users.size > 0) {
users.forEach(doc => {
userList.push(doc.data().userName);
})
}
} catch (error) {
console.log(error);
}
userList.forEach(item => console.log(item)); //usreList contains all of the names. This works.
return userList;
}
return (
<View style={{ flex: 1, justifyContent: 'flex-start' }}>
<Text>Select a character:</Text>
</View>
)
}
export default CharacterSelectScreen;
I'm unsure how I can take the result of the async function userFetch
and assign it to a usable array that I could look over. Maybe something like this:
var names = userFetch();
return (
<ul>
{names.map(function(name, index){
return <li key={ index }>{name}</li>;
})}
</ul>
)
}
});
But maybe this isn't even the best approach. If it's not I am up for learning/changing whatever I just need pointed in the right direction.
Upvotes: 1
Views: 2370
Reputation: 332
A possible solution is to leverage React's diffing algorithm to automatically render the desired user information (through interpolation in your return). This can be done by instantiating a variable that evaluates based on a conditional using your component's current state. If this conditional evaluates to true
it returns a child component for each user in your fetched response (UserIndexItem
).
As for convention, I recommend abstracting your #userFetch
through a container component that passes the method as a property to your CharacterSelectScreen
so that you can DRY up your code better. Example: Use of #useEffect
that sets the state to #userFetch
's return value. This goes in line with separating concerns in your code.
If you want to use what you have, I refactored your original code to execute the solution, let me know how it works for you.
See below for snippet.
import React, { useState } from 'react'
import { StyleSheet, Text, View, TouchableOpacity, TextInput, Button, Picker, ScrollView } from 'react-native'
import { NavigationContainer, useNavigation, useRoute } from '@react-navigation/native';
import * as firebase from '../../firebase';
import UserIndexItem from '...';
// The above import is pseudocode on how you would import a component
const CharacterSelectScreen = ({ navigation, route }) => {
const [ users, updateUsers ] = useState([])
useEffect(() => {
console.log('Select Screen loaded.')
updateUsers( userFetch() )
}, [])
// Great use of #useEffect to simulate React's #componentDidMount
const userFetch = async () => {
try {
let userCollection = []
const fetchedUsers = await firebase.getAllUsers();
// unless #getAllUsers automatically returns JSON then we need to ensure its formatted for parseable data
const usersJSON = await fetchedUsers.json()
usersJSON.forEach( userObject => {
userCollection.push( userObject.data() );
})
return userCollection
} catch (error) {
console.log(error);
}
}
// The below conditional allows us to safely apply
// logic on undefined receivers because of the ternary
// condition. On re-render of our component, this function will
// return child components instead.
const userIndex = users.length != 0 ?
users.map( user => {
// where we can deconstruct the user object for DRYer code
return (
<UserIndexItem key={user.id} username={user.username} />
)
})
: <span> No Users Yet!</span>
return (
<View style={{ flex: 1, justifyContent: 'flex-start' }}>
<ul>
{ userIndex }
</ul>
</View>
)
}
export default CharacterSelectScreen;
For React convention on mapping components from an array, see: https://reactjs.org/docs/lists-and-keys.html
Now, you can do what @AsherLim did in their return block with ghost tags but any inline functions in a component's render function will cause an unintentional re-render of your component every time there is a change to its parent component (This can lead to performance issues). Reference to this claim can be found here: https://medium.com/@Osterberg/react-component-renders-too-often-2917daabcf5
Upvotes: 1
Reputation: 416
If I understand your question correctly, you may require the following pattern:
const Foo = () => {
const [results, setResults] = useState([]);
useEffect(() => {
const asyncFunc = async () => {
const response = await fetch(ENDPOINT);
const { results } = await response.json();
setResults(results);
}
asyncFunc();
}, [deps];
return (
<>
{results.map((result) => <Bar result={result}/>)}
</>
)
}
Upvotes: 1