I am trying to develop a discussion forum website using React, Node and MongoDB.In post object, there is nested author object and tags array.
Here is sample image of a post object:
here is the component which I am trying to render:
import React, { Component } from "react";
import http from "../services/httpService";
import { postEndPoint, repliesEndPoint } from "../config.json";
class PostPage extends Component {
state = {
post: [],
replies: [],
};
async componentDidMount() {
const id = this.props.match.params.id;
const { data: post } = await http.get(postEndPoint + "/" + id);
const { data: replies } = await http.get(repliesEndPoint + "/" + id);
console.log(post.tags, typeof post.tags);
this.setState({ post: post, replies: replies });
}
render() {
const { post, replies } = this.state;
return (
<React.Fragment>
<div className="container col-lg-8 shadow-lg p-3 mt-5 bg-body rounded">
<h2>{post.title}</h2>
<p className="mt-4" style={{ color: "#505050" }}>
{post.description}
</p>
<div className="mt-1">
Related Topics:
{post.tags.map((tag) => (
<span className="badge badge-secondary m-1 p-2">
{(tag).name}
</span>
))}
<h6 className="mt-2">
{post.upvotes.length} Likes {post.views} Views
</h6>
<div class="d-flex w-100 justify-content-between">
<small class="mb-1">Posted by {post.author['name']}</small>
</div>
</div>
</div>
</React.Fragment>
);
}
}
export default PostPage;
This throws the following : TypeError: post.tags is undefined. a Similar error is throws while accessing post.upvotes and post.author
Since you do your http request in 'componentDidMount' a render occured at least once before. So react tried to read post.something and it was still undefined.
And even if you do it before an http request is asynchronous so be careful
You need to check that post.something is defined before you use.
Also your initialisation if confusing you initialize post as an array but you are trying to do post.title.
If post is really an array then post.map() won't crash on an empty array.
If it's an object check that is it defined correctly.
Try this as initial state
state = {
post: {
description:"",
title:"",
tags: [],
author:[] ,
upvotes:[] ,
views : 0
},
}
initial state for post is {}
state = {
post: { tags: [] },
replies: [],
};
You can have a simple if condition added. So it will only loop through that if it is present. Check this.
import React, { Component } from "react";
import http from "../services/httpService";
import { postEndPoint, repliesEndPoint } from "../config.json";
class PostPage extends Component {
state = {
post: [],
replies: [],
};
async componentDidMount() {
const id = this.props.match.params.id;
const { data: post } = await http.get(postEndPoint + "/" + id);
const { data: replies } = await http.get(repliesEndPoint + "/" + id);
console.log(post.tags, typeof post.tags);
this.setState({ post: post, replies: replies });
}
render() {
const { post, replies } = this.state;
return (
<React.Fragment>
<div className="container col-lg-8 shadow-lg p-3 mt-5 bg-body rounded">
<h2>{post.title}</h2>
<p className="mt-4" style={{ color: "#505050" }}>
{post.description}
</p>
<div className="mt-1">
Related Topics:
{post.tags && post.tags.map((tag) => ( // <--- map will only execute when it finds tags.
<span className="badge badge-secondary m-1 p-2">
{(tag).name}
</span>
))}
<h6 className="mt-2">
{(post.upvotes && post.upvotes.length) || 0} Likes {post.views} Views // <---- These default values too will handle the case where the data isnt ready yet
</h6>
<div class="d-flex w-100 justify-content-between">
<small class="mb-1">Posted by {post.author['name']}</small>
</div>
</div>
</div>
</React.Fragment>
);
}
}
export default PostPage;
Related
I am getting this error
Mainsection.js:27 Uncaught TypeError: Cannot read properties of undefined (reading 'map')
Here is my mainsection.js file, I am using API key to iterate the data, I am still not getting the cause of the error. First I had made an array whose name was info and stored all the data in it and then iterated it, Now after using fetchapi, I deleted that array as it was of no use. I don't know whether it was meant to delete or not.
import React, { Component } from 'react'
import Card from './Card'
export default class Mainsection extends Component {
constructor() {
super();
this.state = {
info:null
}
}
async componentDidMount(){
let Url="https://randomuser.me/api/?inc=gender,name,nat,location,picture,email&results=";
let data= await fetch(Url);
let parsedData= await data.json()
console.log(parsedData);
this.setState({
info : parsedData.info
})
}
render() {
return (
<div>
<div className="container mt-5">
<div className="row">
{this.state.info.map((element) => {
return <div className="col-md-4">
<Card key={element.email} name={element.name} location={element.location} gender={element.gender} imageUrl={element.picture.medium} />
</div>
})}
</div>
</div>
</div>
)
}
}
And here is my card.js file
import React, { Component } from 'react'
export default class Card extends Component {
render() {
let {name, location, gender, imageUrl}=this.props
return (
<div>
<div className="card" style={{ width: "18rem" }}>
<img src={imageUrl} className="card-img-top" alt="..." />
<div className="card-body">
<h5 className="card-title">{name}</h5>
<p className="card-text">{location}</p>
<p className="card-text">{gender}</p>
Go somewhere
</div>
</div>
</div>
)
}
}
Please let me I should provide more details
You can find a working example here: https://codesandbox.io/s/musing-hill-uxtt0
There are other issues with your code. For example, the name and location are objects and you are directly trying to show it on UI. I have also added code to fix name.
Mainsection.js
import React, { Component } from "react";
import Card from "./Card";
export default class Mainsection extends Component {
constructor() {
super();
this.state = {
info: null,
results: null
};
}
async componentDidMount() {
let Url =
"https://randomuser.me/api/?inc=gender,name,nat,location,picture,email&results=";
let data = await fetch(Url);
let parsedData = await data.json();
console.log(parsedData);
this.setState({
info: parsedData.info,
results: parsedData.results
});
}
render() {
console.log("results : ", this.state.results);
return (
<div>
<div className="container mt-5">
<div className="row">
{this.state?.results?.map((element) => {
return (
<div className="col-md-4">
<Card
key={element.email}
name={element.name}
location={element.location}
gender={element.gender}
imageUrl={element.picture.medium}
/>
</div>
);
})}
</div>
</div>
</div>
);
}
}
Card.js
import React, { Component } from "react";
export default class Card extends Component {
render() {
let { name, location, gender, imageUrl } = this.props;
return (
<div>
<div className="card" style={{ width: "18rem" }}>
<img src={imageUrl} className="card-img-top" alt="..." />
<div className="card-body">
<h5 className="card-title">{`${name?.title} ${name?.first} ${name?.last}`}</h5>
<p className="card-text">{JSON.stringify(location)}</p>
<p className="card-text">{gender}</p>
<a href="/" className="btn btn-primary">
Go somewhere
</a>
</div>
</div>
</div>
);
}
}
So, the API you are using doesn't response back with an Array. But it responses with an object, that object has 2 attributes which are results and info. as you can see below
{
"results": ...
....
....
"info": ...
}
and results it self is an Array of objects, while info is just an object.
So yes, you cant use map on object, its only useable on arrays.
Check the response carefully so you can decide what you wanna do.
I'm trying to add a search bar to a parent component.
All the logic is working fine in the console. With every character that is typed in the search field I get fewer results.
I try to pass it to a child component to render the card(s) result, but I get a blank card: I can not see data passed.
Parent Component <AllAssets>
class AllAssets extends Component {
state = {
cards: [],
searchField: '',
}
async componentDidMount() {
const { data } = await cardService.getAllCards();
if (data.length > 0) this.setState({ cards: data });
}
addToFavorites = (cardId, userId) => {
saveToFavorites(cardId, userId)
toast.error("The asset was added to your favorites.")
}
render() {
const { cards, searchField } = this.state;
const user = getCurrentUser();
const filteredAssets = cards.filter(card => (
card.assetName.toLowerCase().includes(searchField.toLowerCase())));
console.log(filteredAssets);
return (
<div className="container">
<SearchBox placeholder={"Enter asset name..."}
handleChange={(e) => this.setState({ searchField: e.target.value })}
/>
<PageHeader>Assets available for rent</PageHeader>
<div className="row">
<div className="col-12 mt-4">
{cards.length > 0 && <p>you can also add specific assets to your favorites and get back to them later...</p>}
</div>
</div>
<div className="row">
{!!filteredAssets.length ? filteredAssets.map(filteredAsset => <SearchResult addToFavorites={this.addToFavorites} filteredAsset={filteredAsset} user={user} key={filteredAsset._id} />) :
cards.map(card => <CardPublic addToFavorites={this.addToFavorites} card={card} user={user} key={card._id} />)
}
</div>
</div >
);
}
}
export default AllAssets;
Child Component <SearchResult>
const SearchResult = (addToFavorites, filteredAsset, card, user) => {
return (
<div className="col-lg-4 mb-3 d-flex align-items-stretch">
<div className="card ">
<img
className="card-img-top "
src={filteredAsset.assetImage}
width=""
alt={filteredAsset.assetName}
/>
<div className="card-body d-flex flex-column">
<h5 className="card-title">{filteredAsset.assetName}</h5>
<p className="card-text">{filteredAsset.assetDescription}</p>
<p className="card-text border-top pt-2">
<b>Tel: </b>
{filteredAsset.assetPhone}
<br />
<b>Address: </b>
{filteredAsset.assetAddress}
</p>
<p>
<i className="far fa-heart text-danger me-2"></i>
<Link to="#" className="text-danger" onClick={() => addToFavorites(card._id, user._id)}>Add to favorites</Link>
</p>
</div>
</div>
</div>
);
}
export default SearchResult;
When I console.log(filteredAsset) in <SearchResult> I get an empty object. What am I doing wrong?
This line is incorrect:
const SearchResult = (addToFavorites, filteredAsset, card, user) => {
You are passing in positional arguments, not named props. Do this instead:
const SearchResult = ({addToFavorites, filteredAsset, card, user}) => {
In your original code, React attaches all of your props as fields on the first argument. So they would be accessible in the child, but not in the way you're trying to access them. Try logging out the values of each of the arguments in the child, if you're curious to see what happens.
The corrected version passes in a single object with field names that match the names of your props. It's shorthand that's equivalent to:
const SearchResult = (
{
addToFavorites: addToFavorites,
filteredAsset: filteredAsset,
card: card,
user: user,
}
) => {
**
React Gatsby web app: Uncaught SyntaxError: Unexpected token '<'
**
I am getting this error while running the app. This page is having a button event handler. look at this code given below.
Coursecart.js page.
import React, { Component } from "react"
import Heading from "../Reuseable/Heading"
import Img from "gatsby-image"
const getCategory = items => {
let holditems = items.map(items => {
return items.node.category
})
let holdCategories = new Set(holditems)
let categories = Array.from(holdCategories)
categories = ["all", ...categories]
return categories
}
export default class Coursecart extends Component {
constructor(props) {
super(props)
this.state = {
courses: props.courses.edges,
mycourses: props.courses.edges,
mycategories: getCategory(props.courses.edges),
}
}
cateClicked = category => {
let keepItsafe = [...this.state.courses]
if (category === "all") {
this.setState(() => {
return { mycourses: keepItsafe }
})
} else {
let holdMe = keepItsafe.filter(node => {
return node.category === category
})
this.setState(() => {
return { mycourses: holdMe }
})
}
}
render() {
return (
<section className="py-5">
<div className="container">
<Heading title="Courses" />
<div className="row my-3">
<div className="col-10 mx-auto text-center">
{this.state.mycategories.map((category, index) => {
return (
<button
type="button"
className="btn btn-info m-3 px-3"
key={index}
onClick={() => {
this.cateClicked(category)
}}
>
{category}
</button>
)
})}
</div>
</div>
<div className="row">
{this.state.mycourses.map(({ node }) => {
return (
<div
key={node.id}
className="col-11 col-md-6 d-flex my-3 mx-auto"
>
<Img fixed={node.image.fixed} />
<div className="flex-grow-1 px-3">
<div className="d-flex">
<h6 className="mb-0">{node.title}</h6>
<h6 className="mb-0 text-success ml-3">$ {node.price}</h6>
</div>
<p className="text-muted">
<small>{node.description.description}</small>
</p>
<button
className="btn btn-warning snipcart-add-item"
data-item-id={node.id}
data-item-name={node.title}
data-item-price={node.price}
data-item-url="https://https://rgecom.netlify.app/"
data-item-image={node.image.fixed.src}
>
Join Now
</button>
</div>
</div>
)
})}
</div>
</div>
</section>
)
}
}
This is commonly caused by attempting to parse an XHR response as JSON, when the body actually contains HTML.
That is frequently caused when the request is wrong: a misspelled path component or an unsupported method. Some HTTP server frameworks are configured by default to return a very basic HTML error page for unmatched routes.
So, if you've got a typo in your fetch, and the target server is using the default unmatched-route response, but your code assumes the response will be JSON, you will get this exact error.
Try running this snippet in your JS console, and I bet you'll get the same error:
JSON.parse('<!DOCTYPE html>')
As being designer and novice to react, I developed code in which local array is displaying as image and json data as title. Titles are working fine but images are not displaying and showing all of arrays in src attribute.
I have used Axios.get() to fetch data from the server.
I am missing out in logic somewhere while developing map inside map. I would be grateful for getting help.
EDIT : I want one image with one title.
CommonMeds.js
import React, { Component } from 'react';
import './CommonMeds.scss';
import MedSection from '../../Components/MedSection/MedSection';
import Axios from 'axios';
class CommonMeds extends Component {
state = {
MedTitle: [],
TitleImg: [
{ imageSrc: require('../../../../images/medstype_1.svg') },
{ imageSrc: require('../../../../images/medstype_2.svg') },
{ imageSrc: require('../../../../images/medstype_3.svg') },
{ imageSrc: require('../../../../images/medstype_4.svg') },
{ imageSrc: require('../../../../images/medstype_5.svg') },
{ imageSrc: require('../../../../images/medstype_6.svg') },
{ imageSrc: require('../../../../images/medstype_7.svg') },
{ imageSrc: require('../../../../images/medstype_8.svg') },
{ imageSrc: require('../../../../images/medstype_9.svg') },
{ imageSrc: require('../../../../images/medstype_10.svg') },
]
};
componentDidMount() {
const medInfo = Axios.get('URL OF JSON DATA');
medInfo.then( response => {
this.setState({MedTitle: response.data.result});
});
}
render() {
const Meds = this.state.MedTitle.map(med => {
const imglable = this.state.TitleImg.map(src => {
return src.imageSrc;
})
return <MedSection
Title={med.medicationCategory}
src = {imglable}
/>;
});
return(
<div className="container">
<h3 className="text-left">Common Medicines with Categories</h3>
<hr />
{Meds}
</div>
);
}
}
export default CommonMeds;
MedSection.js
import React from 'react';
import './MedSection.scss';
import MedicineList from '../MedicineList/MedicineList';
const MedSection = (props) => {
return (
<div className="col-12">
<div className="row">
<div className="col-12 col-md-3 col-lg-2 px-0">
<div className="c-medsimg-box py-4 px-2">
<img src={props.src} alt="Medication Type Icon" className="img-fluid" />
<h5 className="mt-3" key={props.key}>{props.Title}</h5>
</div>
</div>
<div className="col-12 col-md-9 col-lg-10">
<div className="h-100 d-flex align-items-center">
<ul className="c-medslist-ul pl-0 mb-0">
<MedicineList />
</ul>
</div>
</div>
</div>
<hr />
</div>
)
}
export default MedSection;
You are currently creating an array of images for each MedTitle. You could instead take the entry from TitleImage that has the same index as the current med in your loop instead.
You can also make it safe by using the % operator, so that if your MedTitle array is larger than your TitleImage array, you will still get an image.
const Meds = this.state.MedTitle.map((med, index) => {
const src = this.state.TitleImg[index % this.state.TitleImg.length].imageSrc;
return <MedSection Title={med.medicationCategory} src={src} />;
});
As long as length of both arrays are same, it will work.
Try using this.
const imglable = this.state.TitleImg.map(src => {
return src.imageSrc;
})
const Meds = this.state.MedTitle.map((med, index) => {
return <MedSection
Title={med.medicationCategory}
src = {imglable[index]}
/>;
});
In Short: I am fetching X JSON Objects from an API, but I only want to display the first 20 Objects, and then have the possibility to fetch another 20 Objects if the user decides to. Whats the best way to do that?
Here is my component, where I am fetching all the teams on componentDidMount and then getting all the JSON objects from my reducer:
import React, { Component } from 'react'
import { APIManager, DateUtils, YouTubeID } from '../../utils'
import actions from '../../actions'
import { connect } from 'react-redux'
import { Link, browserHistory } from 'react-router'
class teams extends Component {
constructor () {
super()
this.state = {
}
}
selectTeam(team, event){
event.preventDefault()
this.props.selectTeam(team)
}
componentDidMount(){
if(this.props.teams != null){
return
}
this.props.fetchTeams()
}
componentDidUpdate(){
if(this.props.teams != null){
return
}
this.props.fetchTeams()
}
render(){
return(
<div id="wrap">
<div style={{height: '65px'}} className="hero hero-teams-2">
<div className="hero-inner group">
<h1 className="center title-long">
<strong>Pro CS:GO </strong>
Teams
</h1>
</div>
</div>
<div style={{paddingTop: '0px'}} id="wrap-inner">
<div id="content" role="main">
<div id="main" className="main-full">
<div className="group">
<div style={{marginLeft: '150px'}} className="portal-group designers">
<h3 style={{marginLeft: '40%'}} className="jump">
Select A Team
</h3>
<div className="scrollable-content-container">
<ol className="portal-list-members debutants scrollable">
{(this.props.teams == null) ? null :
this.props.teams.map((team, i) => {
return (
<li onClick={this.selectTeam.bind(this, team.teamname)} key={i} className="group">
<h3>
<Link to={'/team'} style={{color: '#444', textTransform: 'capitalize'}} className="url hoverable" href="#">
<img style={{height: '160px', width: '160px'}} className="photo" src={"images/"+team.image}/>
{team.teamname}
</Link>
</h3>
</li>
)
})
}
</ol>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
)
}
}
const stateToProps = (state) => {
return {
teams: state.players.teams
}
}
const dispatchToProps = (dispatch) => {
return {
selectTeam: (team) => dispatch(actions.selectTeam(team)),
fetchTeams: (params) => dispatch(actions.fetchTeams(params))
}
}
export default connect(stateToProps, dispatchToProps)(teams)
Mongo Query
router.get('/liveTeams', function(req, res, next){
Team.find({})
.where('status', 'live')
.sort({timestamp : -1})
.exec(function(err, results){
if(err) return next(err);
res.json({
confirmation: 'success',
results: results
})
});
});
MongoDB has a extra method: .limit(20) you can add so you only get max 20 records back.
You can then also use the .skip() method to skip over the previous records.
So if the sorting is Always the same, you would use:
Team.find({}).where('status', 'live').sort({timestamp : -1}).skip(0).limit(20)
the first time.
The next call will be:
Team.find({}).where('status', 'live').sort({timestamp : -1}).skip(20).limit(20)
and so on.