Working with Objects in React

Exploring various ways objects are used within React libraries

This article aims to break down a range of scenarios where objects are used in React and React Native.

The concepts discussed here can also be applied to React JS (React for the web), as the features of the two versions are identical at the component level. Where React for the DOM and React Native differ is their handling of components (pertaining to DOM elements or native mobile components), not in the framework’s defining features like components, props, state, refs, context, and others.

Mobile apps of today rely on complex objects to represent a range of data types and values, that range from component props to server responses, objects to arrays (and converting between the two), functions and classes.

A class in JavaScript (introduced in the ECMAScript6 [2015] edition) is a type of function, declared with the class keyword instead of the function keyword and implements the constructor, introduces context with this, class properties, and the ability for instantiation. An object in JavaScript however is a type of variable that can contain many values. Instantiating classes will therefore instantiate a variable too, albeit with values persisting to the class’s structure.

This piece will firstly discuss object capabilities at a high level, then explore various ways to use and manipulate them along with the syntax used to do so.

Objects at a High Level

To clear up any ambiguity before continuing, an object in JavaScript can be represented like so:

// defining a multi-dimensional objectconst myObj = {
name: 'Ross',
writing: true,
enjoyment: 10,
meta: {
minutesWriting: 20,
minutesProcrastinating: 0,
}
};

Defining an object with const will make it immutable. If the object needs to be manipulated further to get to a final state, use let instead.

Objects are typically defined in camel-case styling, and can be multi-dimensional, like the above example demonstrates with the meta property. Concretely, an object is made up of properties and values — even a class: Running console.log on an instantiated class object will reveal its underlying structure in the same way as the example above, but with more data types within those property values, including functions.

Functions can also be object property values. Consider the following example that defines an object created for a React Context provider, that will allow any component under it to retrieve a toggled boolean, as well as a function to actually toggle that value between true and false:

// configuring objects for React context providersconst [toggled, setToggled] = useState(false);const toggle = () => {
setToggled(!toggled);
const contextValue = {
toggled: toggled,
toggle: toggle,
};

Including functions as object property values is a vital ability to give components access to make changes to context that could potentially effect all the components below that context. Given objects are exhaustive only by hardware limitations, you can make them as complex as you wish.

If a component calls the toggle() function from a context, it will trigger a state change that will then cause a re-render to the components nested below the Context Provider, that’ll consequently reflect their UI to the context state. A good use case for this is toggling between themes, where components could re-render from light to dark mode and vice-versa.

When getting object values, we are not limited to one style of syntax; there are a number of ways we can get property values:

// dot notation
let name = myObj.name
// bracket notation
let minutesWriting = myObj['meta']['minutesWriting']
// dot and bracket notation combined
let procrastinating = myObj['meta'].minutesProcrastinating
// destructuring assignment syntax
const { writing, enjoyment } = myObj
// inline referencing
console.log(myObj.name + ' Writing: ' + myObj.writing);

There are also multiple ways to manipulate an object — assuming that the object is mutable and defined with let:

// dot notation
myObj.name = "James"
// bracket notation
myObj['writing'] = false
// referencing the object with bracket or dot notation
myObj.writing = !myObj.writing

As of ECMAScript 6, it is also possible create objects with custom keys that are derived from variables, with variables able to represent a key and value of a property:

// custom object properties with variables
let key = 'completed'
let value = false;
let myObj = {[key]: value};// manipulate the object via key variables
myObj[key] = true;

I personally either use dot notation where working with properties that do not warrant their own variable reference, or use destructuring when it makes sense to do so.

Destructuring has been great at tidying up syntax in a range of projects I’ve worked on, solving repetition of variable names.

A scenario in React Native where it definitely makes sense to do so is when referencing props at the top of a component:

// destructuring `props` within a componentconst ComponentWithManyProps = (props) => {// immutable `prop` references
const {
name,
writing,
enjoyment
} = props;
// immutable Redux dispatch methods
const {
setName,
setEnjoyment,
updateWritingTime } = props;
// mutable props
let { meta } = props;
...
}

The above example defines reference variables three times, the third statement defining the mutable meta variable. Why would you do so? Simply to organise things better, especially when you are heavily relying on a Redux store as well as many props being passed from the parent component.

You can also destructure deeply embedded property values too, such as taking minutesProcrastinating directly:

const { meta: { minutesProcrastinating } } = myObj;

The above statement does not define a meta variable, only a minutesProcrastinating variable. To also define meta one can do the following:

const { meta, meta: { minutesProcrastinating } } = myObj;

In addition to this, we can use spread syntax to define a subset of an object in a separate variable. Let’s say I wish to work with the object with my name omitted:

const { name, ...rest } = myObj;

The rest variable will now contain everything else apart from name, but my name will also exist as a standalone variable via name.

If you simply wish to a property from an object, the delete keyword can be used. This is what deleting the name property would look like:

// ensure myObj is mutable to delete propertieslet myObj = {
...
}
delete myObj.name;

This section has already covered a lot about how JavaScript objects operate — the next sections will delve more into more use cases where React Native relies on objects. What can concretely be said at this point about Objects in JavaScript is that:

  • Objects are variables that can store many values, including functions and other objects, including instantiated class objects.
  • In JavaScript, classes are just a type of function, and instantiating classes will yield another object, or a variable with many values.
  • Objects can be mutable or immutable via the const or let keywords, that dictate whether you can manipulate the object.
  • JavaScript supports a range of operators to access object values, including destructuring, spread syntax, bracket and dot notation to get object values, and contains special keywords like delete to remove properties from an object.

Rendering Components From Objects

A key method of rendering JSX within React is that of iterating through an object and generating JSX for each item of that object. We can break down our understanding of this concept in two points:

  • The object in question has to be iterable, that often means reformatting the object as an array of objects, or ensuring the object has more than one enumerable property.
  • An iterator, such as map, is then applied to the object in order to enumerate each item and return the resulting JSX. map is used a lot for generating JSX from objects due to the fact it returns a nicely formatted array of JSX items that we can simply embed in a component’s return block.

Any iterator can be used to iterate through objects, such as the for…of loop, for…in loop, and others depending on how you wish to process the object in question — you could even create your own iterator. You’re not limited to just map, but be aware of whether the iterator in question works with objects, or an array of objects.

map is the most widely used iterator in React, and should be the reader’s first port of call for generating JSX from arrays of objects.

To demonstrate this common pattern, think of an object that returns a list of something, such as a list of articles to render within a component. The following object represents a list of articles:

// an array of objects representing articlesconst articlesObj = [
{
name: 'React Navigation 5 Article',
...
}, {
name: 'React Context with Dark Mode',
...
}, {
name: 'All About TypeScript in React'
...
}
];

This object is actually an array of objects, as denoted by the square brackets wrapping the list of objects. articlesObj can now be run through map to generate some JSX for rendering:

const articlesJsx = articlesObj.map((item, index) => {
return (
<View key={index}>...</View>
);
});

map takes one argument — a function — and provides each item and index respectively through each iteration of the object. The resulting array of JSX is then stored as articlesJsx.

Notice that the key prop is a requirement of map, that is required under the hood of React for indexing purposes. You’ll get a warning if you do not include it.

If your object is not an array and you want to run it through map, use Object.values, that returns an array of the object’s enumerable property values:

const articlesJsx = Object.values(articlesObj).map((item, index) => 
<View key={index}>...</View>
);

The return statement and braces / brackets syntax has been removed from the above example, demonstrating that you do not need such boilerplate if you are simply returning JSX.

Now, what if your object is still not an array — such as the myObj object we referenced in the previous section — but you wish to iterate through it? You could use the for..in loop, that enumerates the object’s top-most enumerable properties:

// iterating through an objectfor(const property in myObj) {
console.log(property);
}

But if you specifically wish to use map, you can apply the Object.values method to the object in question to apply the transformation to an array of objects.

It is perfectly valid to embed map directly into a component’s returning JSX, and it often makes sense to construct map() within the return statement itself in order to keep all your JSX on one place. This is achieved by wrapping the function with curly braces:

const MyComponent => (props) => {
const { articles } = props;
return (
<View>
{articles.map((item, index) =>
<View key={index}>...</View>
}

</View>
);
}

This is a lot tidier that defining JSX in a separate block, storing it as a separate variable, before embedding it in the return block. Note that you can even use the curly braces and return syntax (refer to the first map example) to embed more logic within map’s enumerator function before rendering the item’s JSX.

It is also common to nest maps within maps, to expand a sub-array of objects stored as a property value. Consider research papers written by multiple authors, and listing those authors within an articles map. The object might resemble the following:

const articles = [
{
title: 'Article Title',
publishDate: 1589781780,
authors: ['Ross Bulat', 'John Smith', 'Zhang Wei'],
},
...
}

The authors field could have been an array of objects, perhaps to store author credentials and their contributions to the article. This is how we’d map authors within an articles map:

// map within a map in JSXconst MyComponent => (props) => {
const { articles } = props;
return (
<View>
{articles.map((item, i) =>
<View key={i}>
{item.authors.map((author, j) =>
<View key={j}>
{/* Author */}
</View>
}
</View>
}}
</View>
);
}

It is also common to have conditional statements within your JSX, the conditions of which being derived from a property value. This could be to control styling or content. Let’s take the previous example and add two conditionals to it, to highlight prominent authors and style each article row:

// inline conditionals derived from object property{articles.map((item, i) => 
<View key={i} style={i === 0 ? styles.head : styles.row }>
{item.authors.map((author, j) =>
<React.Fragment key={j}>
{
author.isHighlighted
? <HighlightedAuthor author={author} />
: <InlineAuthor author={author} />
}
<React.Fragment />
}
</View>
}

Also notice the use of React.Fragment here, to wrap a conditional that will render one of two components and prevent a duplication of the key prop.

Arrays of Objects and Objects or Arrays

It is evident now that arrays play a big part in working with objects and generating JSX in React. This section highlights are few more ways we can work with them in conjunction with objects.

Sometimes you are not certain that you will actually be working with an array — this is especially the case when working with document based databases such as MongoDB, where document schema is not strictly defined.

To check whether a property value is an Array, the Array.isArray method can be used:

const obj1 = { authors: 'Ross Bulat' }
const obj2 = { authors: ['Ross Bulat', 'Fei Li']}
console.log(Array.isArray(obj1.authors) // > false
console.log(Array.isArray(obj2.authors) // > true

Using Array.isArray can determine whether a property can be mapped or not; it is worth checking when there’s a possibility a certain field may not be an array (and cause a runtime error in your app).

Arrays can get complicated when they are nested in a document. Consider the following structure:

// nested arrays within a documentconst result = [{
title: 'Article Title',
publishedDate: 1589782684,
authors: [{
name: 'Ross Bulat',
pastPapers: [{
title: 'Some React Native Finding',
inJournal: true,
citations: 8,
contributors: [
'Alice',
'Bob',
...
],
},
...
]
},
...
]
},
...
]

This can be tackled with map to some effectiveness, but if you need to do something else like reformat the document or extract values to apply to a reducer, then looping will be required. It is probably wise to use a vanilla for statement in this case, to keep track of the each array’s indexes:

// navigating nested arrays within an objectfor(let i = 0; i < result.length; i++) {
for(let j = 0; j < result[i].authors.length; j++) {
for(let k = 0; k < result[i].authors[j].pastPapers.length; k++) {
for (let l = 0; l result[i]
.authors[j]
.pastPapers[k]
.contributors
.length; l++) {
console.log(result[i].authors[j].pastPapers[k].contributors[l]);
}
}
}
}

If you already know a particular index you need to update or work with, then you can avoid these loops. But oftentimes you are given another unique identifier to work with and are required to find where in the array they exist — that is when the above will be useful.

Duplicating Objects in Redux

If you are working with Redux (the most popular global state management solution for React) then you may be familiar with the concept of making a copy of the state object you are working with before manipulating it in a reducer. This is standard practice in Redux, and in React — you cannot manipulate state directly; you must instead copy that state, make changes, then apply it in the standard way (setState for class components, useState hooks for functional components, dispatch methods and reducers for Redux).

For this reason, you may have seen the Object.assign method used in various code bases. Object.assign copies all enumerable properties from one (or more) objects and applies them to another target object:

const returnedTarget = Object.assign(targetObj, source1, source2, ...);

The above example manipulates an existing object — targetObj — by copying source1 and source2 properties to it. Note that you can apply objects from multiple sources to the target object.

However if we take this approach and define targetObj as an empty object, and not an already existing object, then a brand new object will be returned in the process — this is how a new state object should be created in a reducer function:

let newState = Object.assign({}, previousState);

As well as Object.assign, Object.create is also used to copy all values from one object and assign them to a new object. It is used less than Object.assign in React though — most likely due to Object.assign being able to apply multiple sources to an empty target in one statement.

We are not only limited to copying the entire previousState object; instead we could pick particular properties to apply to the target object:

The following examples assume that the reader is aware of dispatching Redux actions, whereby objects can be sent to the reducer for each dispatch method.

// applying properties to new state from `action`let newState = Object.assign({}, {
writingTime: action.newWritingTime,
);

If you have a nested object within action to apply to the new state, spread syntax can be used to apply all of those object properties:

// applying all nested object properties to new state from `action`let newState = Object.assign({}, {
...action.updatedArticle
);

Finally, if we utilise the multiple sources approach, we can do things like only overwrite a subset of a nested object. Take the following example, that only overwrites the meta.minutesProcrastinating property from the first object we discussed:

// overwriting nested object propertiesconst newObj = Object.assign({}, myObj, {
meta: {
...myObj.meta,
minutesProcrastinating: newMinutesProcrastinating
}
});

The first source object copies the entire myObj object to the target, whereas the second source overwrites the nested meta object by firstly assigning its current values (via spread syntax) before overwriting the minutesProcrastinating field. Hopefully that field won’t be updated too much!

The main takeaway from this section is to work with copies of state, and not the current state object itself, before manipulating it and returning it from a reducer. Object.assign is the most common way of doing this within Redux reducers.

Working with Object IDs

If you are working with document databases like MongoDB, you will have noticed that the unique identifiers for each of your documents are ObjectId types, represented as an _id property.

This ObjectId data type is a standard BSON format that can actually be generated without any MongoDB context. The easiest way to do so within React is via the bson-objectid package, that allows you to generate ObjectIds in a number of ways. Simply install the package, then import it in your project to start using it:

// generating an ObjectIdimport ObjectID from "bson-objectid";const objId = ObjectID();console.log(objId);

Generating an ObjectId is useful for upserting documents (updating if the document exists, inserting otherwise), where you are not certain the document has been inserted or not, but require the _id to work with it within your project. In some cases you may need to know a document _id before inserting it into the database — perhaps for configuring a document in a multi-stage process while committing those updates to the database without any context of whether it has been inserted already.

Although this is more of a specialised case of working with objects, I deemed it useful enough to include it in this piece.

In Summary

This article has aimed to give insights into how Objects in JavaScript are used in React, by demonstrating various ways objects are created, manipulated and navigated within the framework. We’ve covered the basics of objects — what they are in JavaScript, and the syntax used for navigating them.

We then looked at the common map method to iterate through objects and nested objects to generate JSX, both as a variable and inline JSX, that keeps all your JSX within the return statement of your component. Working with arrays of objects were covered, as well as the challenges of working with deeply nested arrays of objects that has become common with the popularity of document-based databases.

Furthermore, the article covered working with Redux, demonstrating how to duplicate a state object to perform changes and finally apply it within a reducer. Finally, the ObjectId data type was mentioned, as well as the bson-objectid package that facilitates generating your own ObjectIds.

Programmer and Author. Director @ JKRBInvestments.com. Creator of LearnChineseGrammar.com for iOS and Android.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store