REACT: Render the keys of only one object

REACT: Render the keys of only one object
0

#1

So I have an object of objects, and I’m trying to render only the keys from the first object. I’ll post the full codepen at the end, but so far I’ve got:

renderKeys = () => {
  return Object.keys(this.props.data).filter((obj) => Object.keys(this.props.data).indexOf(obj) == 0).map(obj => {
    return (
    <div>
     {Object.keys(obj)}   
    </div>
    )
  })
  }

The first part


Object.keys(this.props.data).filter((obj) => Object.keys(this.props.data).indexOf(obj) == 0)

Is being used to isolate the first object, and renders “object1” to the DOM as I’d intended. However, when I try to map over it to get just the keys, like so:

.map(obj => {
    return (
    <div>
     {Object.keys(obj)}   
    </div>
    )
  })

I’m getting 0123456 rendered to the DOM, which for the life of me I cannot figure out. Can anyone shed some light on this?

Full codepen: https://codepen.io/mkedenburg/pen/mGJBjg


#3

The problem with

(this.props.data).filter((obj) => Object.keys(this.props.data).indexOf(obj) == 0)

is that you are returning just the key name object1 and you then you try to map over this. A string is a bit like an array of characters so when you try to get Object.keys of a string it gives you the index of each array item. In javascript under the hood an array is just a keyed object.

["red", "green", "blue"] is the same as

{
   0: 'red',
   1: 'green',
   2: 'blue'
}

Hence when you get the key of this array of letters it give you 01234.

So your solution would be to do something like this:

renderKeys () {
    const firstObject = Object.keys(this.props.data)[0];
    return Object.keys(this.props.data[firstObject])
      .map(key => {
        return <div>{key}</div>;
      });
  };

#4

Be careful with a solution like this, because the order of keys of an object is not necessarily the order in which they appear in the object. For example, if this.props.data was the following object:

{
  object1: {
    name: "name1",
    started: "2018-07-18",
    price: "£100"
  },
  
  2: {
    name: "name2",
    finished: "2018-08-22",
    price: "£100"    
  },
  
  1: {
    prop1: "name3",
    prop2: "2020-01-10",
    prop3: "£1000"
  }
}

then the same code would display:

prop1
prop2
prop3

Why does it not display the keys of the object1 property? Because the ECMAScript 6 specification for how own keys of an object are traversed are as follows:

  1. First, the keys that are “like” integer indices are ascending numeric order.

  2. Then, all other string keys, in the order in which they were added to the object.

  3. Lastly, all symbol keys, in the order in which they were added to the object.

Assuming you know for sure that all of the properties in the this.props.data object are named in such a way that Object.values(this.props.data)[0] will return the first key listed, then you could use this code above.

I would like to make one simplification to @collinstommy’s code. The first line:

const firstObject = Object.keys(this.props.data)[0];

assigns a key (a string) to firstObject and not an object, so the variable name is a little misleading. However, if you use the following line instead:

const firstObject = Object.values(this.props.data)[0];

then firstObject actually references an object, so the return statement would just be:

return Object.keys(firstObject).map(key => <div>{key}</div>);

#5

I agree with what your saying here. I guess the question is here is somewhat of a strange use case. In general if you are using a keyed object to store values, you would be doing so, so that you can access objects by Id. If you wanted to access values in a way where order matters, arrays are a much better mechanism.

A more full proof solution where we need to be sure we always access the first element in a data structure would be to use an array.

A third solution, combining the best of both words, store the order in a separate property.

const Cdata = {
 data: {
 object1: {
    name: "name1",
    finished: "2018-07-18",
    price: "£100",
    paid: "Pending"
  },

  object2: {
    name: "name2",
    started: "2018-08-22",
    price: "£100",
    paid: "Received"
  }
},
byId: ['object1', 'object2', '1', 'anotherObjectKey']
};

// access the keys
const id = this.props.data.byId[0];
const props = Object.keys( this.props.data.data[id]);

Good catch!


#6

In this example, the object is created in the code, so the order and property names can be controlled. The real problem comes in when you retrieve the object via an API as JSON and parse it to an object. Unless, the JSON has the byID property array, it would be risky.

If I was controlling the data manually, then I would to what you did above or use a Map Object instead, which really does combine the both of best worlds (array and object) like so:

const Cdata = new Map([
  ['object', {
    name: "name1",
    finished: "2018-07-18",
    price: "£100",
    paid: "Pending"
  }],
  ['object2', {
    name: "name2",
    started: "2018-08-22",
    price: "£100",
    paid: "Received"
  }],
  [1, {
    prop1: "name3",
    prop2: "2020-01-10",
    prop3: "£1000"
  }]
]);

const firstObj = this.props.data.values().next().value;
const props = Object.keys(firstObj); // props is ['name', 'finished', 'price', 'paid']