How to fix 'Objects are not valid as a React child' - javascript

Codesandbox link.
I'm getting this error when trying to use filter() through a big array of objects (defined as 'filteredCharacters'), and render only those match the id of '6' to the screen (only one does).
I console.log(filteredCharacters), and I can clearly see in console that it works. But for some reason, I'm getting a "Objects are not valid as a React child" error thrown.
The code below is from /components/content.js, in the Codesandbox link above.
import React, { Component } from 'react';
import Intro from '../intro/intro';
class Content extends Component {
render() {
// Grab the 'characters' object from App.js, and assign it to 'this.props'
const { characters } = this.props;
// Filter the chracters and return only whose 'id' belongs to that of '6'
const filteredCharacters = characters.filter(characters => {
if (characters.id === 6) {
return (
<div className="characters" key={characters.id}>
<p>Name: {characters.Name}</p>
<p>ID: {characters.id}</p>
<p>Job: {characters.Job}</p>
<p>Age: {characters.Age}</p>
<p>Weapon: {characters.Weapon}</p>
<p>Height: {characters.Height}</p>
<p>Birthdate: {characters.Birthdate}</p>
<p>Birthplace: {characters.Birthplace}</p>
<p>Bloodtype: {characters.Bloodtype}</p>
<p>Description: {characters.Description}</p>
</div>
)
}
});
// Check to see if it logs properly (it does)
console.log(filteredCharacters);
// When trying to render this to the screen below, it doesn't work
return (
<div>
{filteredCharacters}
</div>
)
}
}
export default Content;

filter will only create a new array with all the elements that returned a truthy value from the function.
You can instead use filter first to get the relevant characters, and then use map on the new array to get the JSX you want to render.
const filteredCharacters = characters
.filter(character => character.id === 6)
.map(character => (
<div className="characters" key={character.id}>
<p>Name: {character.Name}</p>
<p>ID: {character.id}</p>
<p>Job: {character.Job}</p>
<p>Age: {character.Age}</p>
<p>Weapon: {character.Weapon}</p>
<p>Height: {character.Height}</p>
<p>Birthdate: {character.Birthdate}</p>
<p>Birthplace: {character.Birthplace}</p>
<p>Bloodtype: {character.Bloodtype}</p>
<p>Description: {character.Description}</p>
</div>
));

Adding to #Tholle's answer, you could combine those operations into one with reduce
const filteredCharacters = characters
.reduce((acc, character) => {
if (character.id !== 6) return acc;
acc.push(<div className="characters" key={character.id}>
<p>Name: {character.Name}</p>
<p>ID: {character.id}</p>
<p>Job: {character.Job}</p>
<p>Age: {character.Age}</p>
<p>Weapon: {character.Weapon}</p>
<p>Height: {character.Height}</p>
<p>Birthdate: {character.Birthdate}</p>
<p>Birthplace: {character.Birthplace}</p>
<p>Bloodtype: {character.Bloodtype}</p>
<p>Description: {character.Description}</p>
</div>);
return acc;
}, []);

Currently you are using one simple object to behave as a node in the HTML structure of the component you are writing. One of the best practices to use react in such cases is to create and then call this as a react component itself.
Following is your code that now has a separate component that can be called on need:
import React, { Component } from 'react';
import Intro from '../intro/intro';
const FilteredCharcters = characters => {
characters.filter(character => {
if (character.id === 6) {
return (
<div className="characters" key={character.id}>
<p>Name: {character.Name}</p>
<p>ID: {character.id}</p>
<p>Job: {character.Job}</p>
<p>Age: {character.Age}</p>
<p>Weapon: {character.Weapon}</p>
<p>Height: {character.Height}</p>
<p>Birthdate: {character.Birthdate}</p>
<p>Birthplace: {character.Birthplace}</p>
<p>Bloodtype: {character.Bloodtype}</p>
<p>Description: {character.Description}</p>
</div>
)
}
});
class Content extends Component {
render() {
const { characters } = this.props;
return (
<div>
<FilteredCharacters characters={characters} />
</div>
)
}
}
export default Content;

Related

Why I can't get categories?

Does anyone know why I can't filter out the categories?
Error: Objects are not valid as a React child (found: object with keys {_key, _ref, _type}). If you meant to render a collection of children, use an array instead.
I also tried with {categories.title}
when implementing
{categories && categories.map((index, category) => (
<p key={index}>{category}</p>
))}
it just shows 0 in all cards.
import React from 'react'
import Link from 'next/link'
import groq from 'groq'
import sanityClient from '../client'
import imageUrlBuilder from '#sanity/image-url';
function urlFor (source) {
return imageUrlBuilder(sanityClient).image(source)
}
const stories = ({posts}) => {
return (
<div className='bg-gray-100'>
<div className='md:grid md:grid-cols-3 px-4 py-4'>
{posts.length > 0 && posts.map(
({ _id, title = '', slug = '', description, mainImage, categories }) =>
slug && (
<div key={_id} className='py-2 md:px-2'>
<Link href="/post/[slug]" as={`/post/${slug.current}`}>
<div className='border rounded-md p-4 bg-white cursor-pointer'>
<img className='w-full' src={urlFor(mainImage).url()}
width='500'
height='500'
alt="Mainn Image"/>
<h2 className='text-2xl py-6'>{title}</h2>
<p>{categories}</p>
<p className='opacity-60'>{description}</p>
</div>
</Link>
</div>
)
)}
</div>
</div>
)
}
export async function getStaticProps() {
const posts = await sanityClient.fetch(groq`
*[_type == "post" && publishedAt < now()] | order(publishedAt desc)
`)
return {
props: {
posts
}
}
}
export default stories
The main problem is in your code is related to built-in map method of Array constructor. map 's callback function as a first parameter gets an array value and as a second an index of that array item. So you mixed up the order of callback function parameters.
Also you used a category value as a children for JSX element (in your case it's <p>), whose type is probably an object.
For example, if your category looks like this: {id: 0, title: 'Books'}, then you can use as a child or an innerHTML/innerText of <p> like this:
<p key={category.id}>{category.title}</p>,
but not like:
<p key={index}>{category}</p>

Conditional rendering on React.js

render() {
const tableStyle = this.getTableStyle();
const tableSettings = this.getTableSettings();
return (
<div style={tables}>
<TablePosition
contextMenuOn={true}
step={this.props.step}
pdfData={this.props.pdfData}
tableSettings={tableSettings}
tableStyle={tableStyle}
fileName={this.state.fileName}
tableSize={this.getTableSize()}
tableOffset={this.state.tableOffset}
desiredWidth={700}
updateXOffset={x => this.updateXOffset(x)}
updateYOffset={y => this.updateYOffset(y)}
markTable={() => this.markTable()}
setOutputLabels={(row, col, val) => this.setOuputLabels(row, col, val)}
/>
</div>
);
if (!this.props.isThirdStep) {
return (
<div>
<div style={sideBySide}>
<PDFViewer
isThirdStep={this.props.isThirdStep}
paginationCallback={this.handlePageChange}
pdfData={this.state.pdfData}
desiredWidth={600}
selectedPage={this.props.savedPageNo}
/>
</div>
</div>
);
} else {
return (
<div>
<ReferenceMenu />
</div>
);
}
}
In my component's render, I try to render several components based on certain conditions.
So, basically, the TablePoisition always stays there, and the PDFViewer and ReferenceMenu renders conditionally.
However, what I see on both conditions is only the TablePosition component.
Is this not supposed to work?
As explained since you want to combine two components you should change your render logic. One component will be sit there always and the other one will be rendered conditionally. So, you need to render that last component with the sticky one in the same return. I would do something like this:
renderPDFViewer = () => (
<div>
<div style={sideBySide}>
<PDFViewer
isThirdStep={this.props.isThirdStep}
paginationCallback={this.handlePageChange}
pdfData={this.state.pdfData}
desiredWidth={600}
selectedPage={this.props.savedPageNo}
/>
</div>
</div>
);
render() {
const tableStyle = this.getTableStyle();
const tableSettings = this.getTableSettings();
return (
<div>
<div style={tables}>
<TablePosition
contextMenuOn={true}
step={this.props.step}
pdfData={this.props.pdfData}
tableSettings={tableSettings}
tableStyle={tableStyle}
fileName={this.state.fileName}
tableSize={this.getTableSize()}
tableOffset={this.state.tableOffset}
desiredWidth={700}
updateXOffset={x => this.updateXOffset(x)}
updateYOffset={y => this.updateYOffset(y)}
markTable={() => this.markTable()}
setOutputLabels={(row, col, val) => this.setOuputLabels(row, col, val)}
/>
</div>
{
!this.props.isThirdStep
? this.renderPDFViewer()
: ( <div><ReferenceMenu /></div> )
}
</div>
);
}
You need to place your conditional renders inside variables or something similar.
var conditionContent1 = null;
var conditionContent2 = null;
if(condition1){
conditionContent1 = <div>conditional content 1</div>;
}
if(condition2){
conditionContent2 = <div>conditional content 2</div>;
}
return (
<div id="wrapper">
<div>
content
</div>
{conditionContent1}
{conditionContent2}
</div>
);
I added a wrapper div; because, I believe render's return doesn't like having multiple root elements.
If the variables are null; then, it won't affect the overall render.

Lodash to sort data in react

I am trying to order by events based on their date. I have a button that will render the events in ascending order and another button will render the events in descending order. I am using lodash's orderBy method. I am defining the orderDir in the state of my container component. The orderDir changes when the buttons get clicked. The orderBy should be the 'start' property of my data object. I believe I can get to access it. Here is my code:
import React, { Component } from 'react';
import logo from './logo.png';
import './App.css';
import EventCard from './EventCard';
import SampleData from './data/sample-data.json';
import _ from 'lodash';
let dataRaw = SampleData.data.eventSearch.edges;
let data= dataRaw;
class App extends Component {
constructor(props) {
super(props);
this.state = {
data: {data},
defaultSortIndexes: [],
orderDir:'desc'
};
this.sortEevent = this.sortEevent.bind(this);
}
sortEevent(e, type){
if (type === 'asc'){
this.setState({ orderDir:'asc'});
}else{
this.setState({ orderDir:'desc'});
}
}
render() {
let filteredEvents = this.state.data.data;
let orderDir = this.state.orderDir;
console.log(filteredEvents);
filteredEvents = _.orderBy(filteredEvents, (event)=>{
return event['start'];
}, orderDir);//order by
let events = filteredEvents.map((event, i) =>
<EventCard
key={i}
name={event.node.title}
image={event.node.painting.images[0].thumb_url}
venue={event.node.venue.name}
tickets={event.node.tickets_available}
distance={event.distance}
date={event.node.start}
firstName={event.node.artist.first_name}
lastName={event.node.artist.last_name}
artistImage={event.node.artist.images[0].thumb_url}
/>
);//map
console.log(this.state.data)
return (
<div className="App">
<div className="header-wrapper">
<div className="logo-header">
<div className="logo-wrapper">
<img src={logo} className="App-logo" alt="logo" />
</div>
<div className="menu-wrapper">
<a>Events</a>
</div>
</div>
<div className="filters-wrapper">
<div className="filters">
<p>Search Filters:</p>
<button onClick={(e) => this.sortEevent(e, 'asc')} className="green">SORT ASCENDING</button>
<button onClick={(e) => this.sortEevent(e, 'desc')}>SORT DESCENDING</button>
</div>
</div>
</div>
<div className="EventList">
{events}
</div>
</div>
);
}
}
export default App;
I am console logging the filteredEvents, which is the object that I am trying to sort. Here you can see where the 'start' property is.
Thanks so much in advance!
The value of the start element in your object is a string, and not a Date object, so the comparison will be done using strings comparison and not dates.
You can convert the strings to date using:
new Date(event.node.start)
here is the orderBy usage:
filteredEvents = _.orderBy(filteredEvents, (event)=>{
return new Date(event.node.start);
}, orderDir);//order by

ReactJS if prop exists

at the moment I am getting an error with React because I am mapping an array of objects from a REST end point and some of the objects in the array don't have certain props.
An example is that some of my objects have another object within them called imagesand some don't. When my React component tries to render the objects without the images I am left with an error undefined prop.
Here is my component that renders my prop:
const VehicleDetail = (props) => {
return (
<div className={"col-flex-md-3 col-flex-sm-4 col-flex-xs-6 col-flex-media-query vehicle-item " + props.vehicle.brand.name} data-value={props.vehicle.brand.name}>
<div className="vehicle-container">
<img src={"https://s3-eu-west-1.amazonaws.com/pulman-vw-images/uploads/images/thumbnails/" + props.vehicle.offers[0].image.name} />
<h4 className="vehicle-title">
{props.vehicle.model.name}
</h4>
<div className="row-flex">
<div className="col-flex-xs-12 btn-container">
Learn More
</div>
</div>
</div>
</div>
);
};
So as you can see I have an img element with a prop of props.vehicle.offers[0].image.name however this fails as some of my objects don't have this object available.
So far I have tried adding an inline conditional just to see if the object existed first however this failed with an error:
<img src={"https://s3-eu-west-1.amazonaws.com/pulman-vw-images/uploads/images/thumbnails/" + {if (typeof props.vehicle.offers[0].image.name == "object") { props.vehicle.offers[0].image.name }}} />
Here is my component that iterates over each object and renders them to my VehicleDetail component:
// Vehicle List Component
import VehicleDetail from './vehicle-detail.js';
// Create our component
const VehicleList = (props) => {
// Just add props.vehicle to access API data instead of static
const RenderedVehicles = props.vehicles.map(vehicle =>
<VehicleDetail key={vehicle.slug} vehicle={vehicle} />
);
return (
<div className="row-flex center-xs">
{RenderedVehicles}
{console.log(props.brandSelect)}
</div>
);
};
Any idea what the best practice in React JS is to do this? Thanks
Check the length of offers before rendering.
{props.vehicle.offers.length > 0 ? (
<img src={url + props.vehicle.offers[0].image.name} />
) : null}
I'd do it like this. In your vehicle component declare a variable that would hold the thumbnail url.
const VehicleDetail = (props) => {
let vehicleThumbnail = '';
if (props.vehicle.offers[0].image && props.vehicle.offers[0].image.name) {
vehicleThumbnail = props.vehicle.offers[0].image.name;
}
return (
<div className={"col-flex-md-3 col-flex-sm-4 col-flex-xs-6 col-flex-media-query vehicle-item " + props.vehicle.brand.name} data-value={props.vehicle.brand.name}>
<div className="vehicle-container">
<img src={vehicleThumbnail} />
<h4 className="vehicle-title">
{props.vehicle.model.name}
</h4>
<div className="row-flex">
<div className="col-flex-xs-12 btn-container">
Learn More
</div>
</div>
</div>
</div>
);
};
I'd also do the same for the other props.
The normal case of props.vehicle.offers[0] may looks like this:
{
image: {
name: 'hello'
}
}
So, you can use props.vehicle.offers[0].image.name without any error.
But some cases are:
{
anotherProperty: {
}
}
props.vehicle.offers[0].image is null, so props.vehicle.offers[0].image.name equals to null.name, which throw the error.
A simple way to solve the problem is something like this:
{ props.vehicle.offers[0].image && <img src={"https://s3-eu-west-1.amazonaws.com/pulman-vw-images/uploads/images/thumbnails/" + props.vehicle.offers[0].image.name} /> }
If props.vehicle.offers[0].image is null, the statement after && will not be execute, and return nothing to render.
So, it render img element when props.vehicle.offers[0].image is not null, preventing the error.

Rendering an array.map() in React

I am having a problem where I am trying to use array of data to render a <ul> element. In the code below the console logs are working fine, but the list items aren't appearing.
var Main = React.createClass({
getInitialState: function(){
return {
data: dataRecent
}
},
render: function(){
return (
<div>
<ul>
{
this.state.data.map(function(item, i){
console.log('test');
<li>Test</li>
})
}
</ul>
</div>
)
}
});
ReactDOM.render(<Main />, document.getElementById('app'));
What am I doing wrong? Please feel free to point out anything that isn't best practice.
Gosha Arinich is right, you should return your <li> element.
But, nevertheless, you should get nasty red warning in the browser console in this case
Each child in an array or iterator should have a unique "key" prop.
so, you need to add "key" to your list:
this.state.data.map(function(item, i){
console.log('test');
return <li key={i}>Test</li>
})
or drop the console.log() and do a beautiful oneliner, using es6 arrow functions:
this.state.data.map((item,i) => <li key={i}>Test</li>)
IMPORTANT UPDATE:
The answer above is solving the current problem, but as Sergey mentioned in the comments: using the key depending on the map index is BAD if you want to do some filtering and sorting. In that case use the item.id if id already there, or just generate unique ids for it.
You are not returning. Change to
this.state.data.map(function(item, i){
console.log('test');
return <li>Test</li>;
})
let durationBody = duration.map((item, i) => {
return (
<option key={i} value={item}>
{item}
</option>
);
});
Using Stateless Functional Component We will not be using this.state. Like this
{data1.map((item,key)=>
{ return
<tr key={key}>
<td>{item.heading}</td>
<td>{item.date}</td>
<td>{item.status}</td>
</tr>
})}
You are implicitly returning undefined. You need to return the element.
this.state.data.map(function(item, i){
console.log('test');
return <li>Test</li>
})
Best Answer:
import React, { useState } from 'react';
import './App.css';
function App() {
// Array of objects containing our fruit data
let fruits = [
{ label: "Apple", value: "🍎" },
{ label: "Banana", value: "🍌" },
{ label: "Orange", value: "🍊" }
]
// Using state to keep track of what the selected fruit is
let [fruit, setFruit] = useState("⬇️ Select a fruit ⬇️")
// Using this function to update the state of fruit
// whenever a new option is selected from the dropdown
let handleFruitChange = (e) => {
setFruit(e.target.value)
}
return (
<div className="App">
{/* Displaying the value of fruit */}
{fruit}
<br />
<select onChange={handleFruitChange}>
{
fruits.map((fruit) => <option value={fruit.value}>{fruit.label}</option>)
}
</select>
</div>
);
}
export default App;
Add up to Dmitry's answer, if you don't want to handle unique key IDs manually, you can use React.Children.toArray as proposed in the React documentation
React.Children.toArray
Returns the children opaque data structure as a flat array with keys assigned to each child. Useful if you want to manipulate collections of children in your render methods, especially if you want to reorder or slice this.props.children before passing it down.
Note:
React.Children.toArray() changes keys to preserve the semantics of nested arrays when flattening lists of children. That is, toArray prefixes each key in the returned array so that each element’s key is scoped to the input array containing it.
<div>
<ul>
{
React.Children.toArray(
this.state.data.map((item, i) => <li>Test</li>)
)
}
</ul>
</div>
I've come cross an issue with the implementation of this solution.
If you have a custom component you want to iterate through and you want to share the state it will not be available as the .map() scope does not recognize the general state() scope.
I've come to this solution:
`
class RootComponent extends Component() {
constructor(props) {
....
this.customFunction.bind(this);
this.state = {thisWorks: false}
this.that = this;
}
componentDidMount() {
....
}
render() {
let array = this.thatstate.map(() => {
<CustomComponent that={this.that} customFunction={this.customFunction}/>
});
}
customFunction() {
this.that.setState({thisWorks: true})
}
}
class CustomComponent extend Component {
render() {
return <Button onClick={() => {this.props.customFunction()}}
}
}
In constructor bind without this.that
Every use of any function/method inside the root component should be used with this.that
Dmitry Brin's answer worked for me, with one caveat. In my case, I needed to run a function between the list tags, which requires nested JSX braces. Example JSX below, which worked for me:
{this.props.data().map(function (object, i) { return <li>{JSON.stringify(object)}</li> })}
If you don't use nested JSX braces, for example:
{this.props.data().map(function (object, i) { return <li>JSON.stringify(object)</li>})}
then you get a list of "JSON.stringify(object)" in your HTML output, which probably isn't what you want.
import React, { Component } from 'react';
class Result extends Component {
render() {
if(this.props.resultsfood.status=='found'){
var foodlist = this.props.resultsfood.items.map(name=>{
return (
<div className="row" key={name.id} >
<div className="list-group">
<a href="#" className="list-group-item list-group-item-action disabled">
<span className="badge badge-info"><h6> {name.item}</h6></span>
<span className="badge badge-danger"><h6> Rs.{name.price}/=</h6></span>
</a>
<a href="#" className="list-group-item list-group-item-action disabled">
<div className="alert alert-dismissible alert-secondary">
<strong>{name.description}</strong>
</div>
</a>
<div className="form-group">
<label className="col-form-label col-form-label-sm" htmlFor="inputSmall">Quantitiy</label>
<input className="form-control form-control-sm" placeholder="unit/kg" type="text" ref="qty"/>
<div> <button type="button" className="btn btn-success"
onClick={()=>{this.props.savelist(name.item,name.price);
this.props.pricelist(name.price);
this.props.quntylist(this.refs.qty.value);
}
}>ADD Cart</button>
</div>
<br/>
</div>
</div>
</div>
)
})
}
return (
<ul>
{foodlist}
</ul>
)
}
}
export default Result;

Categories

Resources