I'm mapping some arrays in a React project and I return li-tag children, of course React expects a unique key for every dynamic child. However, I don't think I have any unique key... At least, not that I know of. With my data and code (being fetched from https://tmi.twitch.tv/group/user/instak/chatters), is there any key i can pass?
import React, {Component} from 'react';
export default class Overview extends Component{
constructor(props, context){
super(props, context);
this.state = {
chatters: {
moderators: [],
staff: [],
admins: [],
global_mods: [],
viewers: []
}
};
}
componentWillMount() {
fetch('/api/overview') // fetch from Express.js server
.then(response => response.json())
.then(result => this.setState({
chatters: result.chatters
}));
}
render(){
let {chatters} = this.state;
return (
<div>
<h2>Chatters</h2>
<div>
<h3>Moderators</h3>
<ul>
{chatters.moderators.map(chatter => {
return <li key={chatter.key}>{chatter}</li>;
})}
</ul>
</div>
<div>
<h3>Staff</h3>
<ul>
{chatters.staff.map(chatter => {
return <li key={chatter.key}>{chatter}</li>;
})}
</ul>
</div>
<div>
<h3>Admins</h3>
<ul>
{chatters.admins.map(chatter => {
return <li key={chatter.key}>{chatter}</li>;
})}
</ul>
</div>
<div>
<h3>Global Mods</h3>
<ul>
{chatters.global_mods.map(chatter => {
return <li key={chatter.key}>{chatter}</li>;
})}
</ul>
</div>
<div>
<h3>Plebs</h3>
<ul>
{chatters.viewers.map(chatter => {
return <li key={chatter.key}>{chatter}</li>;
})}
</ul>
</div>
</div>
);
}
}
Just use the twitch username. React doesn't need some fancy key, it just needs to be a unique value that stays the same for that individual rendered element.
example:
chatters.viewers.map(chatterName => {
return <li key={chatterName}>{chatterName}</li>;
})
Related
Started learning React, and I'm trying to pass props to another component to render according to a list of values, but I get the noted error while trying so, even though I cannot find difference between my code and a working code.
first component:
const navbarCollection = [
{ id: 1, item: 'Home', ref: '#'},
{ id: 2, item: 'Search', ref: '#'},
{ id: 3, item: 'Login', ref: '#'},
{ id: 4, item: 'Register', ref: '#'},
];
function UnorderedListNavBar() {
return (
<div id='wrapper-navBarList'>
<ul className='navbar-nav mr-auto'>
{
navbarCollection.map((menuItem) => {
return (<ListItem key={menuItem.id} mykey={menuItem.item} value={menuItem.ref} />);
})
}
</ul>
</div>
)
}
export default UnorderedListNavBar;
second component:
function ListItem(id, type, href) {
return (
<div id='wrapper-navBarListItem'>
<li className='list-item active'>
<a className='nav-link' href={href}>test</a>
</li>
</div>
)
}
export default ListItem;
Honestly I tried everything. When I log what I'm sending with the first component to the second one, I get string as an outcome, yet when logging from the second component (or trying to use the data) it shows an object.
Please help me, I'll appreciate any help especially explanations so I can learn.
Thanks a lot!
function ListItem({mykey, value}) {
return (
<div id='wrapper-navBarListItem'>
<li className='list-item active'>
<a className='nav-link' href={value}>test</a>
</li>
</div>
)
}
export default ListItem;
The error you’re facing is because the object you’ve sent to child component is different than what you’re receiving in the child component
U need to destructure your props this way
function ListItem({ id, mykey, href }) {
return (
<div id="wrapper-navBarListItem">
<li className="list-item active">
<a className="nav-link" href={href}>
test {mykey}
</a>
</li>
</div>
);
}
//A more compact way to pass your props
const navbarCollection = [
{ id: 1, item: 'Home', ref: '#'},
{ id: 2, item: 'Search', ref: '#'},
{ id: 3, item: 'Login', ref: '#'},
{ id: 4, item: 'Register', ref: '#'},
];
function UnorderedListNavBar() {
return (
<div id='wrapper-navBarList'>
<ul className='navbar-nav mr-auto'>
{
navbarCollection.map((menuItem) => {
const {id, item, ref} = menuItem;
return <ListItem key={id} {...{id, item, href: ref}} />
})
}
</ul>
</div>
)
}
export default UnorderedListNavBar;
And ListItem component is,
function ListItem(props) {
const {id, type, href} = props
return (
<div id='wrapper-navBarListItem'>
<li className='list-item active'>
<a className='nav-link' href={href}>test</a>
</li>
</div>
)
}
export default ListItem;
You can either have
function ListItem(props) {
return (
<div id='wrapper-navBarListItem'>
<li className='list-item active'>
<a className='nav-link' href={props.href}>{props.item}</a>
</li>
</div>
)
}
or
function ListItem({href, item}) {
return (
<div id='wrapper-navBarListItem'>
<li className='list-item active'>
<a className='nav-link' href={href}>{item}</a>
</li>
</div>
)
}
We have a props received by this component. We can call the attribute of the received component by props.href, props.item etc. But it is troublesome to have "props." every time, so we use {} to destruct the props, then we can just use href, item to call the attributes.
For any of the above component, call it like this:
<ListItem key={menuItem.id} item={menuItem.item} href={menuItem.ref} />
I'm trying to print the properties of Selectedproduct object inside Modal section and every thing works well until it reaches to "description" array property , it shows me "Cannot read property 'map' of undefined". eventhough when I use console.log(Selectedproduct) the description property appears normally,but when I code console.log(Selectedproduct.description) I dont know why it consider it as undefined .can you please tell me why it can't see the description as stand alone property ?
import React, { Component } from "react";
import FormatCurrency from "../Components/util";
import Slide from "react-reveal/Slide";
import Modal from "react-modal";
import Zoom from "react-reveal/Zoom";
import { connect } from "react-redux";
import { GetProducts } from "../Actions/ItemsActions";
import { AddToCart } from "../Actions/CartActions";
class Products extends Component {
constructor(props) {
super();
this.state = {
show: false,
Selectedproduct: {},
};
}
showModal = (product) => {
console.log(product);
this.setState({ show: true, Selectedproduct: product });
};
hideModal = () => {
this.setState({ show: false });
};
componentDidMount() {
this.props.GetProducts();
}
render() {
const { Selectedproduct } = this.state;
return (
<div>
<Slide left cascade={true}>
{!this.props.products ? (
<div> Loading..</div>
) : (
<ul className="products">
{this.props.products.map((product) => (
<li key={product._id}>
<div className="product">
<a href={"#" + product._id}>
<img
src={product.image}
alt={product.title}
onClick={() => this.showModal(product)}
></img>
<p>{product.title}</p>
</a>
<div className="product-price">
<div> {FormatCurrency(product.price)}</div>
<button
onClick={() => this.props.AddToCart(product)}
className="button primary overlay"
>
{" "}
Add to cart
</button>
</div>
</div>
</li>
))}
</ul>
)}
</Slide>
<Modal isOpen={this.state.show} onRequestClose={this.hideModal}>
<Zoom>
<button className="close-modal" onClick={this.hideModal}>
x
</button>
<div className="product-details">
<img
src={Selectedproduct.image}
alt={Selectedproduct.title}
></img>
<div className="product-details-description">
<p>{Selectedproduct.title}</p>
<ul>
{Selectedproduct.description.map((x)=>(<li>x</li>))}
</ul>
<div className="product-price">
<div>{FormatCurrency(Selectedproduct.price)}</div>
<button
className="button primary"
onClick={() => {
this.props.AddToCart(Selectedproduct);
this.hideModal();
}}
>
{" "}
Add to cart
</button>
</div>
</div>
</div>
</Zoom>
</Modal>
</div>
);
}
}
export default connect((state) => ({ products: state.products.filterdItems }), {
GetProducts,
AddToCart,
})(Products);
Try this as your state property seems still undefined at runtime.
{Selectedproduct.description.map((x)=>(<li>x</li>))}
replace with:
{Selectedproduct && Selectedproduct.description? Selectedproduct.description.map((x)=>(<li>x</li>)):null}
description is likely undefined. Instead of:
<ul>
{Selectedproduct.description.map((x)=>(<li>x</li>))}
</ul>
just put in this temporary code to try and see what your object really looks like:
<ul>
console.dir("### DESCRIPTION IS:", Selectedproduct.description)
</ul>
and the open your browser dev tools to see what this prints to the console.
UPDATE based on comment after using console.log:
If you are getting something like availableColors: Array(2) for Selectedproduct you cannot print an array out to your <li> tags. An array is not a string. You have to unnest the inner arrays first.
So if your structure is Selectedproduct.description.availableColors = ['blue', 'red'] just as an example, you will need code like:
const { availableColors, otherAttribute1, otherAttribute2 } = Selectedproduct.description // destructure all array attributes from description
...
and then later in the component, do:
<ul>
{ availableColors.map(_ => <li>_</li>)}
{ otherAttribute1.map(_ => <li>_</li>)}
{ otherAttribute2.map(_ => <li>_</li>)}
</ul>
I have this list:
const chosen = (e: any) => console.log(e.target.dataset.value)
...
<ul>
{numbers.map(n => (
<a data-value={n} onClick={chosen}>
<li key={n}>
{n}
</li>
</a>
))}
</ul>
...
It logs undefined.
Also tried this: console.log(e.target.getAttribute('data-value')) and it returns null.
How do I get the value from a tag?
Stack: TypeScript: 3.8.3, React: 16.13.1
In frameworks like React and Vue you generally stay away from reading data from the DOM when possible. In this case, you can capture the value in a function:
const chosen = (e: any, value: any) => console.log(value)
...
<ul>
{numbers.map(n => (
<a key={n} onClick={(event) => { chosen(event, n); }}>
<li>
{n}
</li>
</a>
))}
</ul>
...
You can use the following code to do that:
export default function App() {
function chosen(event) {
const meta = event.target.parentNode.getAttribute("data-value");
console.log(meta);
}
return (
<ul>
{numbers.map((n) => (
<a data-value={n} onClick={chosen}>
<li key={n}>{n}</li>
</a>
))}
</ul>
);
}
li element should contentin the a element. Please try this example
import React from "react";
const numbers = [1, 2, 3, 4, 5];
function App() {
function chosen(event) {
event.preventDefault();
console.log(event.target.dataset.value);
}
return (
<ul>
{numbers.map((number) => {
return (
<li key={number}>
<a href="!#" onClick={chosen} data-value={number}>
{number}
</a>
</li>
);
})}
</ul>
);
}
export default App;
And follow the Ross Allen advice
I have a a JSON file which I would like to use its content into my React App.
This is an excerpt from my code
export default class App extends Component{
constructor(props){
super(props);
this.state = {
entry : []
}
}
componentDidMount(){
fetch(process.env.PUBLIC_URL + `./js/data.json`)
.then(res => res.json())
.then(json => this.setState({ entry: json }));
}
render(){
return(
<div>
<ul>
{this.state.entry.map( x => (
<li>
<div className='centering'>
<span>
<img alt='' key={x.img} src={process.env.PUBLIC_URL + `./img/${x.img}.jpg`}/>
</span>
<span className='txt'>
{ x.date }
</span>
<span>
<p>{ x.ctx }</p>
</span>
</div>
<div className='hr-line'></div>
</li>
))}
</ul>
</div>
)
}
}
this is the content of my data.json
{
"entry" : {
"2" : [
{
"date":"1/1/1",
"img":"profile",
"ctx":"as"
}
],
"1" : [
{
"date":"1/1/1",
"img":"profile",
"ctx":"as"
}
]
}
When I save the file, on the browser it shows TypeError: this.state is null
onrender {this.state.entry.map (x => ...
Is there something missing, or it is impossible to do so?
Thank you in advance.
Rendering is done before componentDidmount. You need to wait for your data to load before to do your map. Something like :
export default class App extends Component{
constructor(props){
super(props);
this.state = {
entry : [],
anyData: false
}
}
componentDidMount(){
fetch(process.env.PUBLIC_URL + `./js/data.json`)
.then(res => res.json())
.then(json => this.setState( prevState => ({ ...prevState, entry: json, anyData: true })));
}
render(){
if(!anyData) {
return <Loader />
}
return(
<div>
<ul>
{this.state.entry.map( x => (...))}
</ul>
</div>
)
}
}
There is also a possibility to use async lifecycle method :
async componentDidMount() {
const res = await fetch(...);
const json = await res.json();
this.setState( prevState => ({ ...prevState, entry: json, anyData: true }));
}
But it does not change the fact that it will be done after rendering.
React expects the .map for iterative rendering to work on an array of items only.
Therefore, changing the json input to return an array of items will help resolve the problem.
Existing input that does not work with .map:
"entry" : { "1" : [....], "2" : [....] } --> Input is not an array of items.
--> .map does not support this format
How to make it work with .map?
"entry" : [ {"1" : {....} , {"2" : {....} ] --> Input is an array of items.
--> .map works with this format
Additional: How to avoid 'null value' error in render method?
The render method must explicitly check for null condition before trying to render elements. This can be done as follows:
render(){
return(
<div>
<ul>
{ (this.state.entry) &&
this.state.entry.map( x => (
<li>
...
</li>
))
}
</ul>
</div>
)
}
Hey I'm quite new to programming and I'm trying to solve this exercise and I got stuck.
Show 10 posts from the API in the browser - Working
For each post show 3 related comments - Working
The problem is that when I click one Post from the feed, the click function will fetch and display the all other comments below the respective posts at the same time...What I'm trying to accomplish is to onClick display comment to the related post and hide it when clicked on other post.
Also I need to show a button "load more" every time a set of comments appears and fetch the latest 10 comments when clicked.
Any Help, Suggestions on how to keep things clean and readable would be appreciated!
Thank you in advance;
:)
Code Below:
import React from "react";
import axios from "axios";
const postsID = "/posts";
const commentsID = "/comments";
var postsURL = `https://jsonplaceholder.typicode.com${postsID}`;
var commentsURL = `https://jsonplaceholder.typicode.com${commentsID}`;
class Posts extends React.Component {
constructor(props) {
super(props);
this.state = {
posts: [],
comments: [],
expanded: false,
commentsToShow: 3
};
this.clicked = this.clicked.bind(this);
}
/*
showMoreComments() {
}
*/
clicked() {
axios.get(commentsURL).then(res => {
console.log("comments:", res);
this.setState({ comments: res.data });
});
}
componentDidMount() {
axios.get(postsURL).then(res => {
console.log("posts:", res);
this.setState({ posts: res.data });
});
}
render() {
//console.log('VARIABLE WORKING!', postsURL);
return (
<div className="container">
<div className="jumbotron-div col s12">
<ul className="collection">
{this.state.posts.slice(0, 10).map(post => (
<div>
<div key={post.id} onClick={this.clicked}>
<h5>User ID: {post.id}</h5>
<p>Post: {post.body}</p>
</div>
<div>
<ul className="collection">
{this.state.comments
.filter(comment => comment.postId === post.id)
.slice(0, 3)
.map(comment => (
<li key={comment.id}>
<p>Comment ID: {comment.postId}</p>
<p>Comment: {comment.body}</p>
</li>
))}
</ul>
</div>
</div>
))}
</ul>
</div>
</div>
);
}
}
export default Posts;
If a Post can show its comments or hide it, then it definetly needs its own state. Therefore, it needs to be an own component, e.g.:
class Post extends React.Component {
constructor(props) {
super(props);
this.state = { showComents: false };
}
render() {
const { id, body, comments } = this.props;
return (
<div key={id} onClick={() => this.setState({showComments: true })}>
<h5>User ID: {id}</h5>
<p>Post: {body}</p>
</div>
<div>
<ul className="collection">
{this.state.showComments ? comments.slice(0, 3)
.map(comment => (
<li key={comment.id}>
<p>Comment ID: {comment.postId}</p>
<p>Comment: {comment.body}</p>
</li>
)) : ""}
</ul>
</div>
</div>
))}
);
}
}
Then use <Post /> inside of Posts and pass down all the data the Post needs.