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.
Related
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>
I have a Component that renders a list of elements using the map function. Each element is rendered with a delete and edit button. I have added the delete functionality, but I'm having problem with the edit one.
The functionality that I want is: click on edit item, replace H3 element (which is the title) with an input field and let the user update the name. I've tried replacing an element with another but this only works for the first element of the list, because I get the element with 'getElementById' I have tried doing it with querySelector, but that selects only the last element of the array.
I have no idea what to do. I know the issue is selecting the particular element at the right index. I use an id as a key but I don't know how to properly replace the html element. Any help will be vastly appreciated.
Here is where the map function renders the elements:
class Donut extends Component {
render(){
const {donuts, deleteDonut, editDonut} = this.props;
const donutsList = donuts.map((donut) => {
return <div key={donut.id} className="donut">
<div className="name">
<img src={donut.image} />
<div id="donut-name">
<h3 id="donut-title">{donut.name}</h3>
<p>{donut.date}</p>
</div>
</div>
<div className="price">
<p>{donut.price}</p>
<img src="img/edit.png" id={donut.id} onClick={()=>{editDonut(donut.id)}} />
<img src="img/delete.png" id={donut.id} onClick={() => {deleteDonut(donut.id)}} />
</div>
</div>
})
return (
<div>
{donutsList}
</div>
)
}
}
export default Donut
Try to avoid as much as possible directly manipulating DOM elements when you using React. In this case, you should use another approach:
Add a field to this class's state: editingDonutId
When you click in a donut, set the editingDonutId to corresponding id and when you finished it, reset the value.
In render function, inside the map, do a condition render to check if current rendering donut has same id with editingDonutId, if true, we render an input instead.
You are using react, not jquery, so do not use getElementById, try react solution.
This is my solution:
class Donut extends Component {
state = {
donutsState: {}
}
setDonutState: (id, value) => {
this.setState((preState) => {
const predonutState = preState.donutsState[id] || {}
return {
donutsState: {
...preState.donutsState,
[id]: {
...predonutState,
...value,
}
}
}
})
}
getDonutState: (id) => this.state.donutsState[id] || {};
render(){
const {donuts, deleteDonut, editDonut} = this.props;
const donutsList = donuts.map((donut) => {
const donutState = this.getDonutState(donut.id)
// when user input the name, save it in the state.
const onChange = (e) => {
this.setDonutState(donut.id, { value: e.target.value })
}
// when click edit, replace h3 with input.
const onEdit = () => {
this.setDonutState(donut.id, { eidt: true })
}
// when enter key, replace input with h3 and submit the name value.
const onKeyDown = (e) => {
if (e.key === 'Enter') {
this.setDonutState(donut.id, { eidt: false })
editDonut(donut.id, {
name: this.getDonutState(donut.id).value || donut.name,
})
}
}
return (
<div key={donut.id} className="donut">
<div className="name">
<img src={donut.image} />
<div id="donut-name">
{
donutState.edit
? <input id="edit-donut-title" value={donutState.value || donut.name} onChange={onChange} onKeyDown={onKeyDown} />
: <h3 id="donut-title">{donut.name}</h3>
}
<h3 id="donut-title">{donut.name}</h3>
<p>{donut.date}</p>
</div>
</div>
<div className="price">
<p>{donut.price}</p>
<img src="img/edit.png" id={donut.id} onClick={()=>{editDonut(donut.id)}} />
<img src="img/delete.png" id={donut.id} onClick={() => {deleteDonut(donut.id)}} />
</div>
</div>
)
})
return (
<div>
{donutsList}
</div>
)
}
}
export default Donut
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;
Redux turned my app state to props and I would like to access the properties of certain props once the API request is done. Yet, my page can't render as I get the above message.
kicking off GET reqest:
componentWillMount() {
this.props.renderRoles(this.props.params.roleID);
};
Rendering users does work though:
renderUsers(){
return this.props.roleDetails.userList.map((user)=>{
return(
<li className='list-group-item eventUserList' background-color="#f2f2f2" key={user._id}>
{user.userName}
</li>
);
});
};
render() {
const { handleSubmit, fields: {} } = this.props;
This is the problem part. Somehow, properties of this.props get undefined if I look for their properties:
if (this.props.roleDetails.roleName){
return (
If I only look for this.props.roleDetails, it does work, but not when for its properties:
if (this.props.roleDetails){
return (
<div className='container-fluid'>
<form>
<div className="roleDetails">
<div className="roleName">
{this.props.roleDetails.roleName}
</div>
</div>
<div className="roleUsers">
<div className="userDetails">
<h4 className="listOfUsers">Role Owner :</h4>
<ul>
{this.renderUsers()}
</ul>
</div>
</div>
<div>
<div className="submitRole" onClick={this.submitForRole.bind(this)}>Apply</div>
</div>
</form>
</div>
);
}else {
return (
<div>
</div>
);
}
}
}
function mapStateToProps(state) {
return{
roleDetails: state.api.roleDetails,
refreshedRole: state.api.refreshedRole
}
}
export default reduxForm({
form: 'roleView',
fields: []
}, mapStateToProps, actions)(roleView);
Other similar questions are related to functions, but in my case is about accessing props therefore .bind(this) doesn't work.
if (this.props.roleDetails.roleName) looks like roleDetails undefined befor You get data with request, so if You will try to get access to it's property roleName you will get an error.
if (this.props.roleDetails && this.props.roleDetails.roleName) shouldn't throw errors.
And it's not good idea to fetch data in componentWillMount, use componentDidMount instead.
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;