118

New to React and trying to loop Object attributes but React complains about Objects not being valid React children, can someone please give me some advice on how to resolve this problem? I've added createFragment but not completely sure where this needs to go or what approach I should take?

JS

var tifs = {1: 'Joe', 2: 'Jane'};
var tifOptions = Object.keys(tifs).forEach(function(key) {
    return <option value={key}>{tifs[key]}</option>
});

Render function

render() {
        const model = this.props.model;

        let tifOptions = {};

        if(model.get('tifs')) {
            tifOptions = Object.keys(this.props.model.get('tifs')).forEach(function(key) {
                return <option value={key}>{this.props.model.get('tifs')[key]}</option>
            });
        }

        return (
            <div class={cellClasses}>

                    <div class="grid__col-5 text--center grid__col--bleed">
                        <h5 class="flush text--uppercase">TIF</h5>
                        <select id="tif" name="tif" onChange={this.handleChange}>
                            {tifOptions}
                        </select>
                    </div>

            </div>
        );
    }

Error in console

If you meant to render a collection of children, use an array instead or wrap the object using createFragment(object)
styler
  • 15,779
  • 23
  • 81
  • 135
  • 1
    I got a similar error like this before. Using `.map()` oddly enough was the fix for me. This was interesting also: http://stackoverflow.com/questions/37997893/promise-error-objects-are-not-valid-as-a-react-child – justDan Oct 10 '16 at 19:56

8 Answers8

236

The problem is the way you're using forEach(), as it will always return undefined. You're probably looking for the map() method, which returns a new array:

var tifOptions = Object.keys(tifs).map(function(key) {
    return <option value={key}>{tifs[key]}</option>
});

If you still want to use forEach(), you'd have to do something like this:

var tifOptions = [];

Object.keys(tifs).forEach(function(key) {
    tifOptions.push(<option value={key}>{tifs[key]}</option>);
});

Update:

If you're writing ES6, you can accomplish the same thing a bit neater using an arrow function:

const tifOptions = Object.keys(tifs).map(key => 
    <option value={key}>{tifs[key]}</option>
)

Here's a fiddle showing all options mentioned above: https://jsfiddle.net/fs7sagep/

tobiasandersen
  • 8,480
  • 3
  • 28
  • 39
38

You can use it in a more compact way as:

const tifs = {1: 'Joe', 2: 'Jane'};
...

return (
   <select id="tif" name="tif" onChange={this.handleChange}>  
      { Object.entries(tifs).map((t,k) => <option key={k} value={t[0]}>{t[1]}</option>) }          
   </select>
)

And another slightly different flavour:

 Object.entries(tifs).map(([key,value],i) => <option key={i} value={key}>{value}</option>)  
Banzy
  • 1,590
  • 15
  • 14
15

When I use Object.entries with map, I use Array Destructuring like the following and just call them directly.

Object.entries(tifs).map(([key, value]) => (
    <div key={key}>{value}</div>
));
Felix Htoo
  • 874
  • 12
  • 15
7
const tifOptions = [];

for (const [key, value] of Object.entries(tifs)) {
    tifOptions.push(<option value={key} key={key}>{value}</option>);
}

return (
   <select id="tif" name="tif" onChange={this.handleChange}>  
      { tifOptions }          
   </select>
)
Oleg Zinchenko
  • 503
  • 7
  • 16
  • I ended up needing to use `.entries` instead of `.map` to avoid this error from react: `Element implicitly has an 'any' type because index expression is not of type 'number'` – slim Mar 31 '21 at 14:24
5

you could also just have a return div like the one below and use the built in template literals of Javascript :

const tifs = {1: 'Joe', 2: 'Jane'};

return(

        <div>
            {Object.keys(tifOptions).map((key)=>(
                <p>{paragraphs[`${key}`]}</p>
            ))}
        </div>
    )
Ben Rawner
  • 401
  • 1
  • 5
  • 3
4

You can use map function

{Object.keys(tifs).map(key => (
    <option value={key}>{tifs[key]}</option>
))}
Baqer Naqvi
  • 6,011
  • 3
  • 50
  • 68
2
{
   Object.entries(tifOptions).map(([key, value], index) =>
       console.log("Key" + Key + "::>" + "Value" + value);
)}
  • 2
    This code-only answer is not very useful, considering the age of the question and the other upvoted answers here. If you have a more modern solution, add commentary to your post explaining why this is a better way to do things in 2022. Link to documentation, benchmarks, etc. Pasting this code here is not helping anyone. The OP has long solved their problem, and a person finding this post in 2022 isn't going to need the exact solution here, but an *understanding*. – Chris Baker Jun 29 '22 at 23:35
-9

I highly suggest you to use an array instead of an object if you're doing react itteration, this is a syntax I use it ofen.

const rooms = this.state.array.map((e, i) =>(<div key={i}>{e}</div>))

To use the element, just place {rooms} in your jsx.

Where e=elements of the arrays and i=index of the element. Read more here. If your looking for itteration, this is the way to do it.

alex1997
  • 431
  • 1
  • 5
  • 13
  • 1
    When using an object, the keys are often IDs rather than indexes. If, however, the keys are all indexes, I agree with you. – tobiasandersen Oct 10 '16 at 20:16
  • I appreciate your comment, but I'm not talking about an objects. Working with array seems the best for me and it's also good provided by the docs. Keys are not neccessary at all, they are meant to keep the order of your elements. I didn't invent the indexes method for keys, this is written in the docs :P – alex1997 Oct 11 '16 at 18:37
  • 1
    Arrays are not always the best choice, even though they often are. When caching stuff e.g., I usually turn to Map:s or objects. And while React will add keys, based on indexes, when you don't provide them explicitly, there're definitely use cases for doing so. – tobiasandersen Oct 11 '16 at 21:33
  • This doesn't answer the question at all – reggaeguitar Mar 06 '20 at 21:27