I have a News feature where user can post their status. However, so far, I cannot make it display all comments of the news as my current solution only allows two-level only. Here are my codes:
News.js (for all)
function News() {
const {userId, setUserId} = useContext(UserContext);
const {type, isUserType} = useContext(UserTypeContext);
const [newsList, setNewsList] = useState([]);
const rootNews = newsList.filter(
(newList) => newList.reply_of === null
);
const getReplies = commentId => {
return newsList.filter(newList => newList.reply_of === commentId);
}
useEffect(()=>{
Axios.get('http://localhost:3001/news',{
})
.then((response) => {
if(response.data.length > 0){
setNewsList(response.data);
}
})
},[]);
return (
<div className = "news p-5">
<h3 className="news-title">News</h3>
<div className = "comments-container">
{rootNews.map((rootNew) => (
<div>
<Comment key={rootNew.news_id}
comment={rootNew}
replies={getReplies(rootNew.news_id)}/>
</div>
))}
</div>
</div>
)
}
Comment.js (for rendering the comments and replies)
function Comment({comment, replies}) {
return (
<div className="comment">
<div className="comment-image-container">
<img src = "/user-icon.png" />
</div>
<div className="comment-right-part">
<div className="comment-content">
<div className="comment-author">
{comment.user_name}
</div>
<div>{comment.day}, {moment(comment.date).format('DD-MM-YYYY')} at {comment.time}</div>
</div>
<div className="comment-text">{comment.news_title}</div>
{replies.length > 0 && (
<div className="replies">
{replies.map(reply => (
<Comment comment={reply} key={reply.news_id} replies={[]}/>
))}
</div>
)}
</div>
</div>
)
}
This is an example on how the comment structure would look like:
Comment 1
Reply 1.1
Reply 1.1.1
Reply 1.1.1.1
Reply 1.2
Comment 2
An idea on how I could render infinite replies, or possibly set the maximum level of replies allowed? Thank you
You just need a little change to the Comment component to recursively render the already nested data structure:
function Comment({ comment, newsList }) {
const replies = newsList.filter((newList) => newList.reply_of === comment.news_id);
return (
<div className="comment">
<div className="comment-image-container">
<img src="/user-icon.png" />
</div>
<div className="comment-right-part">
<div className="comment-content">
<div className="comment-author">{comment.user_name}</div>
<div>
{comment.day}, {moment(comment.date).format("DD-MM-YYYY")} at{" "}
{comment.time}
</div>
</div>
<div className="comment-text">{comment.news_title}</div>
{replies.length > 0 && (
<div className="replies">
{replies.map((reply) => (
<Comment key={reply.news_id} comment={reply} newsList={newsList} />
))}
</div>
)}
</div>
</div>
);
}
Basically, you just move the code that gets the direct replies to a comment into the Comment component.
When you render the root Comment component, all direct replies to the root comment will be identified and will cause the rendering of nested Comment components, which will in turn identify the replies to the reply, render a nested Comment component and so on.
Related
I wonder if something like this is doable in reactjs (i'm new).
In this example, when user hovers over the email address (at the bottom), it says: copy to clipboard, once clicked, email is copied.
https://shapefarm.net/contact/
I have the styling, once hovered, the text appears, but I really don't know how to implement the functionality, any ideas? Thank you!
const Footer = () => {
return (
<>
<FooterContainer>
<FooterBg>
<div className="footer-wrapper">
<div className="info">
<div className="hovertext-container">
<p className="hovertext-p1">
mymail#test.com
</p>
<p className="hovertext-p2">copy to clipboard</p>
</div>
</div>
<div>
<SocialMedia />
</div>
</div>
</FooterBg>
</FooterContainer>
</>
)
}
export default Footer
A simple
onClick={() => {navigator.clipboard.writeText(this.state.textToCopy)}}
is going to do the job, you can also try out this npm package.
const Footer = () => {
let email = "mymail#test.com";
return (
<>
<FooterContainer>
<FooterBg>
<div className="footer-wrapper">
<div className="info">
<div className="hovertext-container">
<p className="hovertext-p1">
mymail#test.com
</p>
<p onClick={async () => {
await navigator.clipboard.writeText(email)
alert("Copied text!")
}}
className="hovertext-p2">copy to clipboard</p>
</div>
</div>
<div>
<SocialMedia />
</div>
</div>
</FooterBg>
</FooterContainer>
</>
)
}
export default Footer
I have a React component set up to map through a JSON file of projects and display the information in a card. However some of projects have less information that others. Namely my wordpress website does not need a link to the code. However I have it set up like:
Code:
<p>{project.code}</p>
How can I change this to an if statement, saying if Code is a property then return that block of code or else do not display 'code:' at all.
Here is my two files for reference.
Projects.js:
import React from "react";
import ReactCardFlip from "react-card-flip";
import Data from "../../ProjectData.json";
import './Projects.scss'
import '../MediaQueries/Projects.scss'
const CardStyle = {
padding: "30px",
margin: "30px",
width: "250px",
height: "300px",
};
const Card = ({ project }) => {
const [isFlipped, setIsFlipped] = React.useState(false);
// console.log(project);
return (
<div className="Card-wrapper">
<ReactCardFlip isFlipped={isFlipped} flipDirection="horizontal">
<div
style={CardStyle}
onMouseEnter={() => setIsFlipped((prev) => !prev)}
className="CardFront"
>
<div className="CardFront-div1">
<h3 className="CardFront-div1-title">{project.title}</h3>
<img width="250" src={project.gif} alt="" className="CardFront-div1-gif"/>
<div className="CardFront-div1-list">
<p>{project.html}</p>
<p>{project.css}</p>
<p>{project.javascript}</p>
<p>{project.react}</p>
</div>
</div>
</div>
<div
style={CardStyle}
onMouseLeave={() => setIsFlipped((prev) => !prev)}
className="CardBack"
>
<div>
<p>{project.description}</p>
<span>
Project:
<p>{project.link}</p>
</span>
<span>
Code:
<p>{project.code}</p>
</span>
</div>
</div>
</ReactCardFlip>
<button onClick={() => setIsFlipped((prev) => !prev)} className="cardflip-button">Flip</button>
</div>
);
};
const Projects = () => {
return (
<>
<h1>Projects</h1>
<div className="Projects" id="Projects">
{Data.map((item, index) => (
<Card project={item} key={`card-${index}`} />
))}
</div>
</>
);
};
export default Projects;
{
project.code ? (
<span>
Code:
<p>{project.code}</p>
</span>
) : null
}
Post got deleted by the user but the code is what I was looking for:
{!!project.code && (
<span >
Code:
<p>{project.code}</p>
</span>
)}
What I`m doing wrong?It also says: "Check the render method of Card" , which is here:
<div className="grid-container">
{pokemonData.map((pokemon, i) => {
console.log(pokemon.id) // unique numbers are here
return <Card key={pokemon.id} pokemon={pokemon} />
})}
</div>
Card component itself:
function Card({ pokemon }) {
return (
<div className="card">
<div className="card__image">
<img src={pokemon.sprites.front_default} alt="Pokemon" />
</div>
<div className="card__name">
{pokemon.name}
</div>
<div className="card__types">
{
pokemon.types.map(type => {
return (
<div className="card__type" style={{backgroundColor: typeColors[type.type.name]}}>
{type.type.name}
</div>
)
})
}
</div>
<div className="card__info">
<div className="card__data card__data--weight">
<p className="title">Weight:</p>
<p>{pokemon.weight}</p>
</div>
<div className="card__data card__data--height">
<p className="title">Height:</p>
<p>{pokemon.height}</p>
</div>
<div className="card__data card__data--ability">
<p className="title">Abilities:</p>
{/* {console.log(pokemon.abilities)} Temporary for dev puprose */}
{pokemon.abilities.map(ability => <p>{ability.ability.name}</p>
)}
</div>
</div>
</div>
);
}
export default Card;
You can use the index of the array may be your data is having some kind of duplicate. It is recommended that you pass a key prop whenever you are returning a list.
<div className="grid-container">
{pokemonData.map((pokemon, i) => {
console.log(pokemon.id) // unique numbers are here
return <Card key={i} pokemon={pokemon} />
})}
</div>
Equally, check this segment of card components.
{
pokemon.types.map((type,i) => {
return (
<div key={i} className="card__type" style={{backgroundColor:
typeColors[type.type.name]}}>
{type.type.name}
/div>
)
})
}
And
<div className="card__data card__data--ability">
<p className="title">Abilities:</p>
{/* {console.log(pokemon.abilities)} }
{pokemon.abilities.map((ability, i) => <p key={i}>{ability.ability.name}
</p>
)}
</div>
Previous answer will solve your problem. However, for your info, I would also like to add here.
For React a key attribute is like an identity of a node/element/tag which helps React to identify each item in the list and apply reconciliation correctlyon each item. Without a key React will render your component but may cause issue when you re-order your list.
React recommends to use id of the data instead of index number. However, if your list does not re-orders/ sorts or do not have id then you can use index.
You can read more here:
https://reactjs.org/docs/lists-and-keys.html
Change this:
<div className="card__types">
{
pokemon.types.map(type => {
return (
<div className="card__type"
style={{backgroundColor:typeColors[type.type.name]}}
>
{type.type.name}
</div>
)
})
}
</div>
to:
<div className="card__types">
{
pokemon.types.map((type, key) => {
return (
<div key={key} className="card__type"
style={{backgroundColor:typeColors[type.type.name]}}
>
{type.type.name}
</div>
)
})
}
</div>
and:
{pokemon.abilities.map(ability => <p>{ability.ability.name}</p>
to:
{pokemon.abilities.map((ability,key) => <p key={key} >{ability.ability.name}</p>
I am trying to nest maps to render an array within an object
My Cards Component Render Method (Not Nested, Working):
render() {
return (
<div class="mediator-container">
{this.state.routeList.map(
(route, index) =>
<Card busName={this.state.routeList[index].$.tag} />
)}
<span class="loader">
<span class="loader-inner"></span>
</span>
</div>
);
}
My Cards Component Render Method (Nesteing, Not Working!!):
render() {
return (
<div class="mediator-container">
{this.state.routeList.map((route, index) =>
{
{
this.busTitleToDirectionName(this.state.routeList[index].$.tag).map(busDir => {
<Card busName={busDir} />;
});
}
}
)}
<span class="loader">
<span class="loader-inner"></span>
</span>
</div>
);
}
busTitleToDirectionName(int) returns an array of Strings
My Card Subcomponent's render method:
render() {
// Logging to see if render method is called
console.log("Ran");
return (
<div className="card">
<div className="card-header">
<div className="bus-name">
<p>{this.props.busName}</p>
</div>
</div>
</div>
);
}
How it looks like without nesting when it does work (Not enough reputation to post images so here are the links):
https://i.gyazo.com/66414925d60701a316b9f6325c834c12.png
I also log in the Card subcomponent so that we know that the Card component was ran and it logs that it did get called without nesting
https://i.gyazo.com/fb136e555bb3df7497fe9894273bf4d3.png
When nesting, nothing renders and the Card subcomponent isn't being called as there is no logging of it
https://i.gyazo.com/38df248525863b1cf5077d4064b0a15c.png
https://i.gyazo.com/d6bb4fb413dfc9f683b44c88cce04b8a.png
You can try below code in your nested case. In the nesting of map you have to wrap your nested map within a container. Here I use React.Fragment (<> ) as a container.
return (
<div class="mediator-container">
{this.state.routeList.map((route, index) =>
<>
{
this.busTitleToDirectionName(this.state.routeList[index].$.tag).map(busDir => {
<Card busName={busDir} />;
});
}
</>
)}
<span class="loader">
<span class="loader-inner"></span>
</span>
</div>
);
Hope it will help you!!
Thanks, Prahbat Kumar, but I figured out the issue. I had to return the subcomponent from the nested map here is the code:
render() {
return (
<div class="mediator-container">
{this.state.routeList.map((route, index) =>
this.busTitleToDirectionName(this.state.routeList[index].$.tag).map(busDir => {
return <Card busName={busDir} />
})
)}
<span class="loader">
<span class="loader-inner"></span>
</span>
</div>
);
}
To set the context, I am new to REACT. I am working on a sample app where I need to display the trade data in tabular format. If you select the row, delete button on the extreme right should be displayed. Otherwise it should be hidden.
Instead of toggling with the display button, I am just trying to show and hide a text first. I was able to do that. Only thing which happens is that the text gets toggled for all the rows.
I was trying number of things to get this working. The code below is work in progress and I am not sure what to do next,
import React from 'react';
class TradeTableView extends React.Component {
constructor(){
super()
this.state = {
data: []
}
this.handleClickEvent = this.handleClickEvent.bind(this);
}
handleClickEvent(event) {
const name = event.target.name;
console.log('Name in handleClickEvent is ' + name);
}
componentDidMount() {
console.log('inside component did mount..');
fetch('http://localhost:3004/row')
.then(response => {
return response.json();})
.then(responseData => {console.log(responseData); return responseData;})
.then((data) => {
// jsonItems = JSON.parse(items);
this.setState({data: data});
});
}
render() {
return(
<div className="Table">
<div className="Heading">
<div className="Cell">
<p>Trade Date</p>
</div>
<div className="Cell">
<p>Commodity</p>
</div>
<div className="Cell">
<p>Side</p>
</div>
<div className="Cell">
<p>Quanity</p>
</div>
<div className="Cell">
<p>Price</p>
</div>
<div className="Cell">
<p>Counterparty</p>
</div>
<div className="Cell">
<p>Location</p>
</div>
<div className="Cell">
<p></p>
</div>
</div>
{this.state.data.map((item, key) => {
let prop1 = 'shouldHide'+key;
console.log('The prop is ' + prop1);
return (
<div ref={prop1} key={key} className="Row" onClick={this.handleClickEvent}>
<div className="Cell">
<p>{item.a}</p>
</div>
<div className="Cell">
<p>{item.b}</p>
</div>
<div className="Cell">
<p>{item.c}</p>
</div>
<div className="Cell">
<p>{item.d}</p>
</div>
<div className="Cell">
<p>{item.e}</p>
</div>
<div className="Cell">
<p>{item.f}</p>
</div>
<div className="Cell">
<p>{item.f}</p>
</div>
<div className="Cell">
<p onClick={this.handleClickEvent}>{this.state.prop1 ? 'Sample Text':'Some Text'}</p>
</div>
</div>
)
})}
</div>
);
}
}
export default TradeTableView;
This is my component. If you can let me know how to toggle the text only for a particular row, I can most probably use that know how to toggle the button(my original use case) and then delete the row on click of the button. I will really appreciate your help.
P.S I was able to toggle the text.You may not find logic on thi.state.prop1 in my code above because I was trying to modify the code to make it work for a single row. And finally I got in a state where it is not working for all the rows and obviously not for a single row. To sum up, my problem is to identify the unique row and dislay a section only for that row.
I've added a currentSelectedRow property to state to keep track of the selected row. Below is the code with minimal configuration.
import React from 'react';
import { render } from 'react-dom';
import Hello from './Hello';
class TradeTableView extends React.Component {
constructor() {
super()
this.state = {
data: [{id:1,val:11},{id:2,val:22},{id:3,val:33}],
currentSelectedRow : -1
}
}
handleClickEvent = (event, key) =>{
this.setState({ currentSelectedRow: key})
}
render() {
return (
<div className="Table">
{this.state.data.map((item, key) => {
return (
<div key={key} className="Row" onClick={(e) => this.handleClickEvent(e, key)}>
<div className="Cell">
<p>{item.id}</p>
</div>
<div className="Cell">
<p>{item.val}</p>
</div>
<div className="Cell">
<p onClick={(e) => this.handleClickEvent(e, key)}>{this.state.currentSelectedRow === key ? 'Row Selected' : 'Row Not Selected'}</p>
</div>
</div>
)
})}
</div>
);
}
}
render(<TradeTableView />, document.getElementById('root'));
Here is the working example
Change your handleClickevent like this:
handleClickEvent(event, index) {
const name = event.target.name;
console.log("index is", index);
const stateData = [...this.state.data];
stateData[index]= "some new value"
this.setState({
data: stateData,
})
console.log('Name in handleClickEvent is ' + name);
}
change how you are attaching click handler to
onClick={(event) => this.handleClickEvent(event, key)}
Something along these lines is needed as per my understanding of the question.