m00saca
m00saca

Reputation: 363

Rendering an array of objects React

Good afternoon,

I am trying to display data that is provided to my application by an instance of MongoDB before the initial render. I have yet been successful in doing so either running into errors or warnings.

This is the part of the render method I am working with.

        <div className="right-column">
            <div className="pipeline">

              {this.props.tournamentList.map((x,i) => {
                return <li key={i}>{x.name}</li>
              })}

            </div>

        </div>

this.props.tournamentList has a value of an array of objects like so:

tournamentList:
Array[15]
0:
{…}
1:
{…}
2:
{…} ...

This list comes to my application through the componentWillMount lifecycle method, so before the initial render. To me I should be able to iterate through the array and make a dynamically generated list of tournaments provided by my database.

Yet with the code I provided I am getting this warning:

Uncaught (in promise) Error: Objects are not valid as a React child (found: object with keys {prelims, outRounds, notes}). If you meant to render a collection of children, use an array instead or wrap the object using createFragment(object) from the React add-ons. Check the render method ofBuildTournament.

I tried this approach, creating called displayTournaments and calling it inside the div with the class of "pipeline" but nothing happens, no errors no render:

displayTournaments(){

        const { tournamentList } = this.props;

        tournamentList.map((x,i) => {
                    return <li key={i}>{x.name}</li>
                  })


    }

Clearly I am doing something wrong but I don't know what. Is this an instance where I should be using keyed fragments as suggested by the error message? Would anyone smarter than myself be willing to lend some insight?

Cheers.

Upvotes: 1

Views: 736

Answers (3)

Kyle Richardson
Kyle Richardson

Reputation: 5645

You are going to need to have a loading state if you get your data in the componentWillMount or componentDidMount lifecycle hooks. The below example will illustrate how this is done.

class ComponentThatGetsAsyncData extends PureComponent {
    constructor( props ) {
        super( props );

        this.state = {
            tournamentList: [ ]
        }
    }

    componentDidMount() {
        // use any http library you choose
        axios.get( "/some_url" )
            .then( ( res ) => {
                // this will trigger a re-render
                this.setState({
                    tournamentList: res.data
                });
            })
            .catch( ( err ) => {
                // handle errors
            });
    }

    render() {
        const { tournamentList } = this.state;
        // i'd use something other than index for key

        // on your initial render your async data will not be available
        // so you give a loading indicator and when your http call
        // completes it will update state, triggering a re-render
        return (
            {
                tournamentList ?
                    tournamentList.map((x,i) => {
                        return <li key={i}>{x.name}</li>
                    }) :
                    <div className="loading">Loading...</div>
            }
        );
    }
}

Upvotes: 1

Brady Edgar
Brady Edgar

Reputation: 543

Here would be a simple solution that would have a loading state, error state, and success state.

The first thing to note is you will need to use Object.keys() to your object in order to map over the array of keys since you cannot map plain objects. You should also note that the map will return the key of each object so in order to target key values pairs you will need to use a syntax like this tournaments[key].name rather than just doing tournament.name as you are targeting an object with in an object and then grabbing the value.

Let me know if you need any more help with this

import React from 'react'
import Loader from '../Loader'

const resultList = ({ tournaments, isFetching = true }) => (
<div>
    {
        isFetching
            ?   <div>
                    <Loader /><br />
                    <span>Loading&hellip;</span>
                </div>
            :   <div>
                    {
                        Object.keys(tournaments).length
                        ?   <div>
                                {
                                  tournaments.map((key) => (
                                        <section id={tournaments[key].id} key={key}>
                                            <p>{tournaments[key].name}</p>
                                        </section>
                                    ))
                                }
                            </div>
                        :   <div>
                                <p>There are no tournaments....</p>
                            </div>
                    }
                </div>
    }
</div>
);

export default resultList

Upvotes: 1

rimraf
rimraf

Reputation: 4126

Update: Sorry, I misunderstood your question. Kyle is correct with the loading state.

In addition, using a library like lodash will allow you to map over objects in a more natural manner. The native javascript map method doesn't handle objects all that well.

https://www.npmjs.com/package/lodash

you use it much the same way. just

import _ from lodash

then

_.map(objectToMap, (x) => <Component key={x}>{x.thing}</Component>)

Upvotes: 2

Related Questions