Nothing was returned from render. This usually means a return statement is missing. Or, to render nothing, return null.
I Have an issue related to this message. This is my component that causes the error:
import React from 'react';
import Thumbnail from './Thumbnail';
const AlbumList = ({ results }) => {
let collections = [];
let albums = { };
Object.values(results).map((item, i) => {
if(!collections.includes(item.collectionId)) {
collections.push(item.collectionId);
albums[i] = {id: item.collectionId, cover: item.artworkUrl100}
}
return albums;
});
Object.values(albums).forEach(element => {
return (
<Thumbnail source={element.cover} />
)
})
}
export default AlbumList;
Thumbnail is just a basic Component like:
import React from 'react';
const Thumbnail = ({source}) => {
return(
<div>
<img src={source} alt="album cover"/>
</div>
)
}
export default Thumbnail;
I've been looking for the error like an hour or so.
What am I missing?
Notice that map returns a list while forEach returns undefined.
You don't return anything from your functional Component Plus forEach does not return anything, instead change it to map.
Also you need to set the unique key to Thumbnail component in loop
return Object.values(albums).map((element, index) => {
return (
<Thumbnail key={"Key-"+index} source={element.cover} />
)
})
I would suggest to read this article map vs. forEach
Related
I am trying to build out a component in React which takes information from a JSON source, and uses some of that information to create states which can be passed down into other separate components. While I haven't passed my states into separate components yet, I have been able to get my state to update with the information from the JSON. However, when I load my page I get an error code which I want to sort out before continuing with my project in case there are unintended side effects from leaving the error in my code. The error code reads as following:
index.js:1 Warning: Each child in a list should have a unique "key" prop.
Check the render method of FetchData
in div (at FetchData.js:27)
in FetchData (at App.js:8)
in div (at App.js:7)
My App.js looks like this:
import React from 'react';
import './App.css';
import FetchData from './Components/FetchData/FetchData';
function App() {
return (
<div className="App">
<FetchData/>
</div>
);
}
export default App;
My FetchData.js looks like this:
import React from 'react';
class FetchData extends React.Component {
constructor() {
super();
this.state = {
portrait: null,
title: null
};
}
componentDidMount() {
fetch('https://randomuser.me/api')
.then (response => {
return response.json();
})
.then (data => {
let userImage = data.results.map((person) => {
return (
<div>
<img alt='portrait' img src={person.picture.large}/>
</div>
)
})
let userTitle = data.results.map((person) => { //line 27
return (
<div key={person.results}>
<div> {person.name.title} </div>
</div>
)
})
this.setState ({
portrait: userImage,
title: userTitle
})
console.log(this.portrait, this.title)
})
}
render() {
return (
<div className='box1'>
<div className='box2'>
<h2>{this.state.title}</h2>
{this.state.portrait}
</div>
</div>
)
}
};
export default FetchData;
and just in case since my index.js looks like this:
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
serviceWorker.unregister();
I thought the issue was the fact that I used "person" into both my "data.results.map" so I tried to change the naming but that did not work either. Any help would be greatly appreciated!
The error is referring to your FetchData component.
The reconciliation algorithm in React can work if you assign an unique key to returned DOM objects. In this case, you are returning from the map function a list of similar DOM object. Any returned chunk have to declare the key attribute on the parent node.
In your case:
componentDidMount() {
fetch('https://randomuser.me/api')
.then (response => {
return response.json();
})
.then (data => {
let userImage = data.results.map((person) => {
return (
<div key={person.id}>
<img alt='portrait' img src={person.picture.large}/>
</div>
)
})
let userTitle = data.results.map((person) => { //line 27
return (
<div key={person.id}>
<div> {person.name.title} </div>
</div>
)
})
this.setState ({
portrait: userImage,
title: userTitle
})
console.log(this.portrait, this.title)
})
}
The key value must be an unique string and it is used by React to update the correct DOM nodes on state change. (I don't know how your person.results is filled, but you need a sort of ID)
For simple component you can also use this syntax
let userImage = data.results.map((person,idx) => {
return (
<div key={idx}>
<img alt='portrait' img src={person.picture.large}/>
</div>
)
})
Be aware using this syntax, because idx is the position of the current element in the containing array, and if used more than one time, it results in duplicate keys (And React will think that nodes with same key are the same nodes)
Using Index as a key is an anti-pattern in React.
Never using the index as a key in React, unless:
the list and items are static–they are not computed and do not change
the items in the list have no ids;
the list is never reordered or filtered.
When all of them are met, you may safely use the index as a key.
If not, please use the unique ID.
It may come from the elements you are going to display,
Or you can add a new ID property to your model or hash some parts of the content to generate a key.
The key only has to be unique among its siblings, not globally unique.
Read more here and here in React Docs
In your data.results.map((person)) function you need to add idx prop, so your code should look like:
let userImage = data.results.map((person,idx) => {
return (
<div key={idx}>
<img alt='portrait' img src={person.picture.large}/>
</div>
)
})
You can simply add the key attribute in the elements whenever you are using .map
let userImage = data.results.map((person, index) => {
return (
<div key={index}>
<img alt='portrait' img src={person.picture.large}/>
</div>
)
})
For the value of the key, it is preferred to use an unique id. If you do not have one, you can use index instead.
React Newbie
I am coding in React. I am taking an object of JSON data from a GET request to an api, and trying to pass it as a prop in a component. Then I am mapping over it to make a list of "trail" objects.
I am getting this error in the console:
"Warning: Failed prop type: Invalid prop trail of type array supplied to TrailItem, expected object."
Here's the code for my app level component:
import React, { Component } from "react";
import "./App.css";
import Navbar from "./components/layout/Navbar";
import Trails from "./components/trails/Trails";
import axios from "axios";
class App extends Component {
state = {
trails: {},
};
async componentDidMount() {
const res = await axios.get(
`https://www.hikingproject.com/data/get-trails?lat=35.0844&lon=-106.6504&maxDistance=10&key=${process.env.REACT_APP_HIKING_PROJECT_KEY}`
);
console.log(res.data);
this.setState({ trails: res.data });
}
render() {
return (
<div className='App'>
<Navbar />
<div>
<Trails trails={this.state.trails} />
</div>
</div>
);
}
}
export default App;
As far as I can tell, there is no problem with the data. A console.log(res.data); returns an object, so I know the api request is working.
Here's the code for my "Trails" component:
import React, { Component } from "react";
import TrailItem from "./TrailItem";
class Trails extends Component {
render() {
return (
<div style={trailStyle}>
{Object.keys(this.props.trails).map((key) => (
<TrailItem key={key} trail={this.props.trails[key]} />
))}
</div>
);
}
}
const trailStyle = {
display: "grid",
gridTemplateColumns: "repeat(3, 1fr)",
gridGap: "1rem",
};
export default Trails;
I feel like maybe I'm not using the correct syntax to step into the object, and then further into the "trails" array, but I'm stumped. Thank you for you help!
EDIT
Here is the "TrailItem" code:
import React from "react";
import PropTypes from "prop-types";
const TrailItem = ({ trail: { name, location, imgSmall } }) => {
return (
<div className='card text-center'>
<img src={imgSmall} alt='trail' style={{ width: "25%" }} />
<h3>{name}</h3>
<p>{location}</p>
</div>
);
};
TrailItem.propTypes = {
trail: PropTypes.array.isRequired,
};
export default TrailItem;
I followed the advice of one of the comments and changed the PropType to array, and that fixed one of the warnings. But I still can't get a list of <TrailItem />.
Inside you App render method put this at the start:
if (!this.state.trails.length) {
return <div>Loading...</div>;
}
You can give trails a default or initial value of an empty array so the map function can be invoked. By using an empty array the component will map over an the empty array and return an empty array to render.
Default Value:
const Trails = ({ trails }) => {
console.log(trails.trails);
return (
<div style={trailStyle}>
{trails.map(trail => <TrailItem key={trail.id} trail={trail} />)}
</div>
);
};
TrailItem.propTypes = {
trail: PropTypes.array.isRequired,
};
TrailItem.defaultValue = {
trail: [],
};
Initial Value:
const Trails = ({ trails = [] }) => {
console.log(trails.trails);
return (
<div style={trailStyle}>
{trails.map(trail => <TrailItem key={trail.id} trail={trail} />)}
</div>
);
};
TrailItem.propTypes = {
trail: PropTypes.array.isRequired,
};
Note: This won't fix passing a prop of the incorrect type, but the prop validation react does will. It sounds like you got that bit sorted out though.
I solved it! Granted, I may not have been clear in my original question, but I figured out why I couldn't get access to the data object from the API.
I needed to step into the object one more time upon receiving the response in my App component:
this.setState({ trails: res.data.trails });
Once I did that, in my Trails component I needed Object.key() to make turn the "trails" prop into an array so I could .map() over it.
And finally, the "tricky" part was that I needed to use each "key" as the index for each "trail" prop I was trying to pass to <TrailItem />:
{Object.keys(trails).map((key) => (
<TrailItem key={key} trail={trails[key]} />
))}
I'm super new to React and this might be a dumb question but I'm getting an error saying: Uncaught TypeError: Cannot read property 'map' of undefined
when I'm trying to display the ingredients for each recipe. I'm not sure if I can use an array.map inside of the return statement, or where else should I include it, if it is a part of my component? I just want to go through the ingredients and display them . Thanks for any help]1
import React, { Component } from "react";
class Recipe extends Component {
state = {
activeRecipe: []
}
componentDidMount = async() => {
const title = this.props.location.state.recipe;
const req = await fetch
(`https://api.edamam.com/search?q=${title}&app_id=${API_ID}&app_key=${API_KEY}`);
const res = await req.json();
this.setState({ activeRecipe: res.hits[0].recipe});
console.log(this.state.activeRecipe);
}
render() {
const recipe = this.state.activeRecipe;
return (
<div className="container">
<div className="active-recipe">
<h3 className="active-recipe">{recipe.label}</h3>
<div className="container">
{recipe.ingredients.map(ingredient => {
return (
<p>{ingredient.text}</p>
);
})}
</div>
</div>
</div>
);
}
}
export default Recipe;
This is because your component is rendered/mounted before the async call can finish..
If you change:
recipe.ingredients.map(ingredient => {
to this:
recipe.ingredients && recipe.ingredients.map(ingredient => {
It should work as you are wanting.
This is because map does not get called unless ingredients exist.
I'm passing a map to all my posts variable so that all my posts can appear as a single post, but it kept bringing up the error
I have tried solving it using the Reactjs Documentation from the react website, it shows almost the same code as mine.
import PropTypes from 'prop-types';
import PostItem from './PostItem';
class PostFeed extends Component {
render() {
const { posts } = this.props;
const list = posts.map((post) => <PostItem
key={post._id}
posts={post}
/>
);
return (
{list}
);
}
}
PostFeed.propTypes = {
posts: PropTypes.array.isRequired
};
export default PostFeed;
I expect every posts to appear as a single post from the postItem component
The error means, when your PostFeed mounts for first time at that time props are not available, so the error.
You can check if data available, like,
let list = "Loading...";
if(posts && posts.length > 0){
list = posts.map((post) => <PostItem key={post._id} posts={post} /> );
}
posts is probably result of an async action and its value is not available at the time that your function is doing its job. So it should have a default value or be checked before that has a value.do this:
if(this.props.posts && Array.isArray(this.props.posts) && this.props.posts.length > 0)
//then map
I've been following this tutorial for ReactJS and have been trying now to convert the simplistic Todo App (just checks off and on items) to React Native. I've been using expo to try it live on my phone and everything.
It all went good, but now I'm trying to add something. Whenever I click the checkbox I want to remove the component related to that item.
My idea was:
Since I'm rendering the TodoItem components from an array of todos,
and whenever I click a checkbox it updates the array as a whole
(looking for a certain id and updating it's completed variable). I can
run through the array and whenever the id is different I return the
todo. This way I returned every todo but the one with matching id to
be rendered.
import React, { Component } from 'react';
import { Alert,Image,StyleSheet, Text,Button, View } from 'react-native';
import TodoItem from './TodoItem'
import todosData from "./todosData"
export default class App extends React.Component {
constructor() {
super()
this.state = {
todos: todosData
}
this.handleChange = this.handleChange.bind(this)
}
handleChange(id) {
this.setState(prevState => {
const updatedTodos = this.state.todos.map( todo => {
if(todo.id !== id) {
return todo
}
})
return {
todos:updatedTodos
}
})
}
render() {
const todoItems = this.state.todos.map( item =>
<TodoItem
key={item.id}
item={item}
handleChange = {this.handleChange}
/>
)
return (
<View style={styles.container}>
{todoItems}
</View>
);
}
}
This gives an error: ' TypeError:undefined is not an object (evaluating 'item.id')', giving at App.js:42:18
I'll also add the code referring to the TodoItem:
import React, { Component } from 'react';
import { Alert,Image,StyleSheet, Text,Button, View } from 'react-native';
import { CheckBox } from 'react-native-elements'
function TodoItem(props) {
return (
<View>
<CheckBox
checked={props.item.completed}
onPress={() => props.handleChange(props.item.id)}
/>
<Text>{props.item.text}</Text>
</View>
);
}
export default TodoItem
I don't understand why this won't work. It feels like I'm deleting the component while still using it (for it to give a undefined), but I don't see where. Since I'm simple updating a list of todos.
How can I do the thing I want?
PS: I seem unable to properly format the first segment of code. I apologize for that!
Try this:
handleChange(id) {
const { todos } = this.state
// filter out the deleted one
const filtered = todos.filter(x => x.id !== id)
this.setState({ todos: filtered })
}
We don't want to alter the state directly, but since .filter() creates a new array, without touching the given array, it is fine to use it. if it was another operation, you'd do something like this:
// create a copy
const newSomethings = [...this.state.somethings]
// do whatever with newSomethings
this.setState({ somethings: newSomethings })