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.
Related
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 a react app with a list of Div elements to create some Cards. Each card has 'read more' button to expand and collapse a paragraph and I toggle it for each mouse click. My problem is, for each click, it expand paragraphs in all cards instead only paragraph in the card I clicked. So I can't identify the clicked (this) card.
Component:
class BidCard extends Component {
constructor(props) {
super(props);
this.state = {
readMoreOpen: false,
}
}
readMore() {
this.setState({ readMoreOpen: !this.state.readMoreOpen })
}
render() {
const { articles } = this.props;
return (
articles.map(article => {
return (
<div className="projectCardRoot" key={article.id}>
<div className="projectCardMainLogin">
<div className="projectCardMiddle">
<p className={this.state.readMoreOpen ? 'openFullParagraph' : 'closeFullParagraph'} id="projectCardDesc">{article.description}</p>
<div className="cardReadMore desktopDiv" onClick={this.readMore.bind(this)}>Read more</div>
</div>
</div>
</div>
)
})
)
}
}
export default BidCard;
How can I solve this?
You can save id of the expanded card to the state and the check it when rendering items:
class BidCard extends Component {
constructor(props) {
super(props);
this.state = {
readMoreOpen: [], // Use array here
}
}
// Add card id to the expanded array if not already there, otherwise remove it
readMore = (id) => {
this.setState(state => {
if (state.readMoreOpen.includes(id)) {
return {readMoreOpen: state.readMoreOpen.filter(readId => readId !== id)}
}
return {readMoreOpen: [...state.readMoreOpen, id]}
})
}
render() {
const { articles } = this.props;
return (
articles.map(article => {
return (
<div className="projectCardRoot" key={article.id}>
<div className="projectCardMainLogin">
<div className="projectCardMiddle">
{/*Check if the item is in expanded items array */}
<p className={this.state.readMoreOpen.includes(article.id) ? 'openFullParagraph' : 'closeFullParagraph'} id="projectCardDesc">{article.description}</p>
<div className="cardReadMore desktopDiv" onClick={() => this.readMore(article.id)}>Read more</div>
</div>
</div>
</div>
)
})
)
}
}
You will need to keep expanded state per every card.
I would recommend to create component for card
articles.map(article => {
return (
<Article key={article.id} {...article} />
)
})
)
class Article extends Component {
state = {
readMoreOpen: false
}
readMore() {
this.setState(state => ({ readMoreOpen: !state.readMoreOpen }))
}
render () {
const {description} = this.props;
return (<div className="projectCardRoot" >
<div className="projectCardMainLogin">
<div className="projectCardMiddle">
<p className={this.state.readMoreOpen ? 'openFullParagraph' : 'closeFullParagraph'} id="projectCardDesc">{description}</p>
<div className="cardReadMore desktopDiv" onClick={this.readMore.bind(this)}>Read more</div>
</div>
</div>
</div>)
}
}
Other approach is to keep array of booleans with information of which article div should be currently expanded in this method you will need to update state with id of expanded article
readMore(id) {
this.setState({ articles: this.props.articles.map(article => article.id === id ? true : false) } )
}
and in render use boolean from state as information if it should be expanded
That's because all your cards currently share the same source of truth. You used a ternary operator to determine what class a Card would have depending on the state-value. Well, all Cards are using the same state-value to compare, so understandably, if one is affected, then all would be too.
There's more than one way to resolve this, but the most appropriate would probably be to create a separate Card Component. This makes it so each Card component has their own state to keep track of.
See working sandbox: https://codesandbox.io/s/quizzical-mahavira-wz8iu
Parent.js
import React from "react";
import ReactDOM from "react-dom";
import Card from "./Card";
import "./styles.css";
class BidCard extends React.Component {
render() {
const { articles } = this.props;
return articles.map(article => {
return <Card article={article} />;
});
}
}
BidCard.defaultProps = {
articles: [{ description: "woof" }, { description: "meow" }]
};
const rootElement = document.getElementById("root");
ReactDOM.render(<BidCard />, rootElement);
Card.js
import React, { useState } from "react";
const Card = ({ article }) => {
const [readOpen, setReadOpen] = useState(false);
return (
<div className="projectCardRoot" key={article.id}>
<div className="projectCardMainLogin">
<div className="projectCardMiddle">
<p
className={readOpen ? "openFullParagraph" : "closeFullParagraph"}
id="projectCardDesc"
>
{article.description}
</p>
<div
className="cardReadMore desktopDiv"
onClick={() => setReadOpen(!readOpen)}
>
Read more
</div>
</div>
</div>
</div>
);
};
export default Card;
I did a few modifications to your code. This way it should work.
I added comments that explain the the changes. The main idea is that you should not simply store the boolean readMoreOpen status (which in your code is treated as a kind of shared between all the cards) but specific card identity.
My changes works if there could be only one "expanded" card at any moment. If your design supposes that there could be a few "expanded" cards at the same time the solution would be more complex though not much.
class BidCard extends Component {
constructor(props) {
super(props);
// the way you've tried to keep status (open/closed) it wasn't tied to any speciifc card
// you should store this specific card instead
this.state = {
//readMoreOpen: false,
expandedCard: null,
}
this.readMore = this.readMore.bind(this);
}
readMore(article) {
//this.setState({ readMoreOpen: !this.state.readMoreOpen })
this.setState({expandedCard: article})
}
render() {
const { articles } = this.props;
const { expandedCard } = this.state;
return (
articles.map(article => {
// the look of each card depends on state.expandedCard only if article == expandedCard it's shown with 'openFullParagraph' class
return (
<div className="projectCardRoot" key={article.id}>
<div className="projectCardMainLogin">
<div className="projectCardMiddle">
<p className={article == expandedCard ? 'openFullParagraph' : 'closeFullParagraph'} id="projectCardDesc">{article.description}</p>
<div className="cardReadMore desktopDiv" onClick={() => this.readMore(article)}>Read more</div>
</div>
</div>
</div>
)
})
)
}
}
export default BidCard;
I am new to react and I am working on a flight project.
I am attempting to update a shopping cart when items are selected. I am aware the structure of the code might not be the best way, but this is what I have so far.
Export class DestinationPrices extends React.Component {
constructor(props) {
super(props);
this.state = {
flightData: [],
checked:false,
data:'',
cart:[]
};
this.handleClick = this.handleClick.bind(this);
this.handleAddToCart = this.handleAddToCart.bind(this);
}
NavCart() {
return (
<div className="Nav" >
<Card body >
<CardTitle ><h3><b>Flights Selected : </b></h3></CardTitle>
<CardText >
<span className="fa fa-cart-plus fa-2x"> {this.props.cart.length}</span>
</CardText>
</Card>
</div>
)
}
That is the cart itself which should update number of items when selected.
Below is the Select method which you can select the different cards.
Select(FD) {
this.state={route:''};
return (
<div>
{FD.FlightID}
<label>
<Checkbox id={FD.FlightID}
name={FD.FlightID}
checked={this.setState.route}
onChange={(()=> this.handleCheckboxChange(FD.FlightID))}
handleClick={()=>{this.handleClick(FD.FlightID)}}
/>
<span>Select</span>
</label>
</div>
)
}
These are the functions I have to handle the changes
handleCheckboxChange =(id) =>{
var data;
const selected = (e => e.FlightID = id);
this.handleAddToCart(id);
};
handleAddToCart(flight) {
const cartItem = this.state.cart.find(x => x.id === flight.id);
this.setState({cart: [...this.state.cart, flight]})
}
handleClick(flight) {
this.handleAddToCart(flight)
}
If anyone is able to help it would be greatly appreciated.
could you please tell me how to implement tabs in react with using any library ?
I follow this link and tried to make tabs
https://toddmotto.com/creating-a-tabs-component-with-react/
but not succeeded.
Here is my code:
https://codesandbox.io/s/D9Q6qWPEn
import React, {Component} from 'react';
class Tabs extends Component {
constructor() {
super();
this.state= {
selected:0
}
}
_renderContent() {
return (
<div className="tabs__content">
{this.props.children[this.state.selected]}
</div>
);
}
render() {
return (
<div className="tabs">
{this._renderContent()}
</div>
)
}
}
export default Tabs;
I am not able to show tabs and it there individual contents after click
any update ?
If I understand your problem correctly, you just want to display labels for your tabs.
You can do this in many ways in React but without changing your code too much, you can accomplish this task by changing render() method of Tabs component, like below:
setTab(index) {
this.setState({
selected: index,
});
}
_renderLabels() {
return this.props.children.map((child, index) => (
<div key={child.props.label} onClick={() => { this.setTab(index) }}>
{child.props.label}
</div>
));
}
render() {
return (
<div className="tabs">
{this._renderLabels()}
{this._renderContent()}
</div>
);
}
What we do here is basically rendering labels based on props.children. For each label we append click handler.
You could improve this code by creating specific components like TabPane, TabLabel.
Full code of Tabs component.
import React, { Component } from "react";
class Tabs extends Component {
constructor() {
super();
this.state = {
selected: 0
};
}
setTab(index) {
this.setState({
selected: index,
});
}
_renderContent() {
return (
<div className="tabs__content">
{this.props.children[this.state.selected]}
</div>
);
}
_renderLabels() {
return this.props.children.map((child, index) => (
<div key={child.props.label} onClick={() => { this.setTab(index) }}>
{child.props.label}
</div>
));
}
render() {
return (
<div className="tabs">
{this._renderLabels()}
{this._renderContent()}
</div>
);
}
}
export default Tabs;
Demo: https://codesandbox.io/s/pRvG6K0r
I implemented this and is working awesome:
I'm using a functional component with Hooks.
const Tabs = () => {
const [currentTab, setCurrentTab] = useState('first-tab');
const toggleTab = () => {
switch (currentTab) {
case 'first-tab':
return <FirstTab />;
case 'second-tab':
return <SecondTab />;
default:
return null;
}
};
return (
<div>
<div>
<h3 onClick={() => setCurrentTab('first-tab')}>First Tab</h3>
<h3 onClick={() => setCurrentTab('second-tab')}>Second Tab</h3>
</div>
{toggleTab()}
</div>
);
};
You can add more components if you want.
Psdt: And if you want to add styles to the Current Tab Title you can create another useState Hook, something like "isActive" and this set it up on the tag classes.
const Tabs = () => {
const tabsData = [
{ id: 0, tabName: "first tab", tabData: "first tab data" },
{ id: 1, tabName: "second tab", tabData: "second tab data" },
{ id: 2, tabName: "third tab", tabData: "third tab data" },
];
const [activeIndex, setActiveIndex] = useState(false);
const tabClick = (index) => {
setActiveIndex(index);
};
return (
<>
<ul>
{tabsData.map((tab) => {
return (
<li key={tab.id} onClick={() => tabClick(tab.id)}>
{tab.tabName}
</li>
);
})}
</ul>
<ul>
{tabsData.map((tab) => {
if (tab.id === activeIndex) {
return <li key={tab.id}>{tab.tabData}</li>;
}
})}
</ul>
</>
);
};
if all you have is a hammer, everything looks like a nail
Don't try to use React for everything. Not only is this a bad practice. React was never intended to fit that purpose. Creating tabs is 90% CSS and only 10% JS (controlling which one gets the "active" state.
I created an example that implements tabs 100% using CSS, It doesn't work well since only after a tab is clicked the content is shown, there is no default.
If you wanted to automate this (e.g.) in an React component The only thing you have to do is bind a tab click to an active state a classname representing the active state (e.g.: tab--active). And create a default.
.tabs__list-item {
display: inline;
}
.tab {
display: none;
}
.tab:target { /* Only works if tab is clicked, can be fixed with React. */
display: block;
}
<nav class="tabs">
<ul class="tabs__list">
<li class="tabs__list-item"><a class="tabs__link" href="#tab1">Tab 1</a></li>
<li class="tabs__list-item"><a class="tabs__link" href="#tab2">Tab 2</a></li>
<li class="tabs__list-item"><a class="tabs__link" href="#tab3">Tab 3</a></li>
</ul>
</nav>
<section class="tab" id="tab1">
<h1>Tab 1 header</h1>
<p>This is the content of tab 1.</p>
</section>
<section class="tab" id="tab2">
<h1>Tab 2 header</h1>
<p>This is the content of tab 2.</p>
</section>
<section class="tab" id="tab3">
<h1>Tab 3 header</h1>
<p>This is the content of tab 3.</p>
</section>
In short:
Write the HTML structure / CSS markup first
Observe missing logic
Implement missing logic using the best tool.
React may be a valid tool for this purpose but start by having React rendering your HTML and CSS. If you can't get the "wiring" logic to work open a question indicating what specific problem you encounter.
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>;
})