Cole Perry
Cole Perry

Reputation: 338

How to display the result of an async function

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:

firebase/index.js

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>.

CharacterSelectScreen.js

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

Answers (2)

Dan der Mensch
Dan der Mensch

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

Asher Lim
Asher Lim

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

Related Questions