ReactJS map through an array with objects - reactjs

I have that part of code
const links = [
{
name: 'How it works',
ref: '/'
},
{
name: 'Calendar',
ref: 'calendar'
},
{
name: 'Contact me',
ref: 'contact'
}
];
const renderLinks = links.map((link, index) =>
<li className="nav-item active" key={index}>
<a className="nav-link" href={link.ref || "#"}>
{link.name}
</a>
</li>
);
However when I try to render it an error is thrown.
render() {
return (
{renderLinks}
);
}
Objects are not valid as a React child (found: object with keys
{renderLinks}). If you meant to render a collection of children, use
an array instead.
As I think, I have to got an array but React thinks there is an object.

React thinks this is an object because you indeed provided an object. This is what it is if you write it without shortcut property notation:
render() {
return {
renderLinks: renderLinks
);
}
Just return renderLinks directly without { }:
render() {
return renderLinks;
}

Related

Conditional Type without using As

I have a variable named movies that can take the form of two shapes defined as the following interfaces:
interface BaseMovie {
id: number;
title: string;
}
interface MultiSearchResult extends BaseMovie {
media_type: "movie" | "tv" | "person";
}
I know that when another variable, triggeredSearch is true, the shape will be MultiSearchResult. The shape will be BaseMovie when triggeredSearch is false.
While using this to render each item
<ul>
{movies.map((movie, i) => (
<li key={i}>{!triggeredSearch ? movie.title : movie.media_type}</li>
))}
</ul>
I receive the following warning:
Property 'media_type' does not exist on type 'BaseMovie | MultiSearchResult'.
Property 'media_type' does not exist on type 'BaseMovie'.ts(2339)
Is there a way, outside of using the as keyword (i.e. (movie as MultiSearchResult).media_type), to declare that a movie is a MultiSearchResult when triggeredSearch is true?
A full example can be found here.
TypeScript can't narrow the type of a variable based on the value of another variable, but it can narrow the type of a union based on one of the union's members. So if you can put triggeredSearch and movies in the same data structure, you can use a discriminated union to distinguish things:
type Example =
{ triggeredSearch: true, movies: null | MultiSearchResult[] }
|
{ triggeredSearch: false, movies: null | BaseMovie[] };
declare const data: Example;
// Side note: Using thea rray index as the key an anti-pattern if the
// arrays contents ever change, more here:
// https://robinpokorny.medium.com/index-as-a-key-is-an-anti-pattern-e0349aece318
const items = data.triggeredSearch
? data.movies?.map((movie, i) => <li key={i}>{movie.media_type}</li>)
: data.movies?.map((movie, i) => <li key={i}>{movie.title}</li>);
const result = <ul>{items}</ul>;
Playground link
If you can't, you can use a type assertion function rather than as, which has the advantage of giving you a runtime error if the assertion is false:
function assertMultiSearchResult(movie: BaseMovie): asserts movie is MultiSearchResult {
if (!("media_type" in movie)) {
throw new Error(`Given 'movie' is not a 'MultiSearchResult');
}
}
Then:
<ul>
{movies?.map((movie, i) => {
let content: string;
if (triggeredSerach) {
assertMultiSearchResult(movie);
content = movie.media_type;
} else {
content = movie.title;
}
return <li key={i}>{content}</li>;
})}
</ul>
More long-winded, but also more typesafe.
Typescript doesn't understand your business context.
Why not just set the media_type property to optional, and type your movies state to MultiSearchResult[] only?
import { useEffect, useState } from "react";
import "./styles.css";
interface BaseMovie {
id: number;
title: string;
}
interface MultiSearchResult extends BaseMovie {
media_type?: "movie" | "tv" | "person";
}
export default function App() {
const [triggeredSearch, setTriggeredSearch] = useState(false);
const [movies, setMovies] = useState<MultiSearchResult[]>([
{ id: 1, title: "Avengers" },
{ id: 2, title: "Eternals" }
]);
useEffect(() => {
setMovies([
{ id: 1, title: "Avengers", media_type: "movie" },
{ id: 2, title: "Eternals", media_type: "movie" }
]);
}, [triggeredSearch]);
return (
<div>
<button onClick={() => setTriggeredSearch(true)}>Trigger Search</button>
<ul>
{movies.map((movie, i) => (
<li key={i}>{!triggeredSearch ? movie.title : movie.media_type}</li>
))}
</ul>
</div>
);
}

Uncaught Error: Objects are not valid as a React child (found: object with keys {product})

I have searched SO but I can't figure out what exactly I'm doing wrong. I haven't worked with functional components in react beyond simple examples so I need a little help. I have a parent component Products:
const Products = () => {
const products = [{ name: 'Potatoes', price: '100', unit: 'KG' }, { name: 'Onion', price: '100', unit: 'KG' }, { name: 'Ginger', price: '100', unit: 'gram' }, { name: 'Ladies Finger', price: '100', unit: 'KG' }];
return (
<div>
{
products.map((product, index) =>
<Product product={product} key={index} />
)
}
</div>
);
};
Products.propTypes = {};
export default Products
The above component renders the child Product:
const Product = ({product}) => {
const classes = useStyles();
return (
<p>{product.name}</p>
);
};
Product.propTypes = {};
export default Product
But I get the error
Uncaught Error: Objects are not valid as a React child (found: object with keys {product}).
If you meant to render a collection of children, use an array instead.
The issue seems to be in my Parent component's return statement but I have no idea what. Any help would be appreciated.
Errors like Objects are not valid as a React child usually arise when your the interpreter misinterprets your syntax.
Try adding parentheses around the returned object in your map callback:
products.map((product, index) => (
<Product product={product} key={index} />
))

Render collection of nested JSON Data

I'm using a template file that takes in data from a JSON and displays it as a list
JSON -
"items":[
{
"name":"a",
},
{
"name":"b",
},
{
"name":"c",
}
]
JS File
var items= this.props.data.items.map(function(items){
return <li key={items.name}><span className={items.name}></span><em>{items.name}</em></li>
})
//where it later gets rendered like so:
<div className="four columns main-col">
<div className="bars">
<ul className="items">
{items}
</ul>
</div>
</div>
However I want to modify the data, so that its more categorized, and will have nested objects
Example:
"categorizedItems":[
{
"type":"a",
"items":[
{
"name":"apple"
},
{
"name":"banana"
}
]
},
{
"type":"b",
"items":[
{
"name":"car"
}
]
}
]
So i thought, since it is a nested JSON object, I will need to map twice, so i tried the following:
var categories= this.props.data.categorizedItems.map(function(category){
var items= category.items.map(function(item){
return <li key={items.name}><span className={items.name}></span><em>{items.name}</em></li>
})
return <ul key={category.type}>{items}</ul>
})
//and i render it the same way
<div className="four columns main-col">
<div className="categories">
{categories}
</div>
</div>
However this gives me errors saying "Error: Objects are not valid as a React child (found: object with keys {name}). If you meant to render a collection of children, use an array instead."
I dont understand how what I am doing (the nested mapping) is different from the original code (single mapping).
I resolved the errors in your code
Try this
import React from "react";
import "./styles.css";
const data = [
{
type: "a",
items: [
{
name: "apple"
},
{
name: "banana"
}
]
},
{
type: "b",
items: [
{
name: "car"
}
]
}
];
const categories = data.map((category) => {
const items = category.items.map(function (item) {
return (
<li key={item.name}>
<span className={item.name}></span>
<em>{item.name}</em>
</li>
);
});
return <ul key={category.type}>{items}</ul>;
});
export default function App() {
return (
<div className="four columns main-col">
<div className="categories">{categories}</div>
</div>
);
}
Here is sandbox link - https://codesandbox.io/s/nervous-noether-80ye9?file=/src/App.js:0-714
the problem in existing code.
Instead of
var categories= this.props.data.categorizedItems.map(function(category){
var items= category.items.map(function(item){
return <li key={items.name}><span className={items.name}></span><em>{items.name}</em></li>
})
return <ul key={category.type}>{items}</ul>
})
Try
var categories= this.props.data.categorizedItems.map(function(category){
var items= category.items.map(function(item){
return <li key={item.name}><span className={item.name}></span><em>{item.name}</em></li>
})
return <ul key={category.type}>{items}</ul>
})

Change a sigle value in an array of objects using useState in React

I'm following a React course and I'm trying to do some experiments around the code to have a better understanding of concepts.
I have some dummy data:
export const data = [
{ id: 1, name: 'john' },
{ id: 2, name: 'peter' },
{ id: 3, name: 'susan' },
{ id: 4, name: 'anna' },
];
and this is my component:
import React from "react";
import { data } from "../../../data";
const UseStateArray = () => {
const [people, setPeople] = React.useState(data);
return (
<>
{people.map((person) => {
const { id, name } = person;
return (
<div key={id} className="item">
<h4>{name}</h4>
</div>
);
})}
<button
type="button"
className="btn"
onClick={() => setPeople([])}
>
Clear Items
</button>
</>
);
};
export default UseStateArray;
The button has an event handler on click which calls setPeople with an empty array (so to remove all of the elements).
I was trying to change the funcionality of such button, trying to change the name of the first element of my array of objects (data) in the following way:
onClick={() => setPeople(people[0].name = 'Frank')}
Doing this, get an error, namely: "TypeError: people.map is not a function".
I think the reason is because I'm not returning an array anymore and therefore map fails to run.
How can I simply change the name (or any value) of an object present in an array?
You are mutating the object
clickHandler = () => {
const newPeople = people.map((person, index) => {
if (index === 0) {
return {
...person,
name: 'Frank'
}
}
return person;
});
setPeople(newPeople);
}
....
....
onClick={clickHandler}
You need to copy the array into a newer version.
Extract the object out of the array using the index property.
Update the field.
function App() {
const [data, setData] = React.useState([
{ name: "Hello", id: 1 },
{ name: "World", id: 2 }
]);
function changeName(idx) {
const newData = [...data];
newData[idx].name = "StackOverFlow";
setData(newData);
}
return (
<div>
{data.map((d, idx) => {
return (
<div>
<p>{d.name}</p>
<button
onClick={() => {
changeName(idx);
}}
/>
</div>
);
})}
</div>
);
}
NOTE :-
Mutation is not allowed on the state, which basically means you cannot change the state directly. Copy the object or an array and do the updates.

Create a multidimensional list from Array Objects

I am trying to create a multidimensional list from object full of arrays from a rest request in Javascript. The issue is my ability iterate over an array of objects. Can someone give me an example on how to turn this data structure into a JSX component?
I am trying to create a list that is wrapped in a div and looks like:
<div>
<lo>
<li>
<ul>
<li>Row Cell</li>
<li>Row Cell</li>
</ul>
</li>
<li>
<ul>
<li>Row Cell</li>
<li>Row Cell</li>
</ul>
</li>
</lo>
</div>
The data structure looks like this,
The function that is set in the React Component is the following,
createBodyDisplay(){
var ar = this.state.data.request.body;
var returnString = '';
for (var key in ar) {
console.log(ar);
if (ar.hasOwnProperty(key)) {
if(ar instanceof Array){
console.log('This is a test to see if there is an array');
} else if (ar instanceof Object){
for (var key1 in ar) {
if (ar.hasOwnProperty(key1)) {
console.log(ar[key1]);
}
}
console.log(ar);
} else {
console.log('Not sure what this is');
}
// returnString= returnString+'<div>';
/// var x = numbers.map(Math.sqrt)
// console.log(ar[key]);
// returnString= returnString+'</div>';
}
}
// console.log(returnString);
return returnString;
}
See sandbox here for live example: https://codesandbox.io/s/confident-heyrovsky-s0zg4
Assuming your data-structure looks something like:
const newData = {
dogs: [
{ type: "row-cell", value: "Golden" },
{ type: "row-cell", value: "Husky" }
],
cats: [
{ type: "row-cell", value: "Feline" },
{ type: "row-cell", value: "Hairless" }
]
};
We can use Object.entries() to cleanly create an array of arrays, for each key-value pair. Then use .map() to create our outer-ordered-list items. And within each group, we will use another .map() to create the unordered-list-items.
Working code:
import React from "react";
import ReactDOM from "react-dom";
import "./styles.css";
class App extends React.Component {
state = {
data: {}
};
componentDidMount() {
const newData = {
dogs: [
{ type: "row-cell", value: "Golden" },
{ type: "row-cell", value: "Husky" }
],
cats: [
{ type: "row-cell", value: "Feline" },
{ type: "row-cell", value: "Hairless" }
]
};
this.setState({
data: newData
});
}
createNestedLists = () => {
const { data } = this.state;
const lists = Object.entries(data).map(([type, arr]) => {
return (
<li>
<ul>
{arr.map(item => {
return (
<li>
{item.type} - {item.value}
</li>
);
})}
</ul>
</li>
);
});
return <ol>{lists}</ol>;
};
render() {
return <div>{this.createNestedLists()}</div>;
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

Resources