ReactJS - map doesn't work for single item - javascript

I have a really trivial problem with map function. I am making a request to my API, which returns the single object. I then want to display the object in render method.
const api = axios.create({
baseURL: `https://localhost:5001/api/v1/`
})
class SinglePost extends Component {
state = {
post: []
}
constructor(props) {
super(props);
api.get(`posts/9a6b5be6-b3ef-4c2d-a36b-08da14c62914`).then(res => {
console.log(res.data)
this.setState({ posts: res.data })
})
}
render() {
return (
<div>
{
this.state.post.map(x => <h2>{x.title}</h2>)
}
</div>
)
}
}
export default SinglePost;
The console.log() of res.data seems to display the data just fine.
{
"id": "9a6b5be6-b3ef-4c2d-a36b-08da14c62914",
"name": null,
"title": "Teeest",
"urlTitle": null,
"content": "123",
"created": "2022-04-02T18:30:55.1536762",
"description": "123",
"score": 0,
"isDeleted": false,
"currentFlagStatus": 0,
"flagReason": null,
"userId": "9ecac069-8cfc-4cac-8056-87093fb9c57c",
"authorName": null,
"authorProfilePic": null,
"hashtags": [
{
"id": "a8bc782c-7f5e-4dfc-220c-08da1355d3ec",
"hashtagName": "byq",
"hashtagNameInLower": "byq",
"amountOfHashtagFollowers": 0
},
{
"id": "a5efd6b1-cff0-40b5-2218-08da1355d3ec",
"hashtagName": "Test",
"hashtagNameInLower": "test",
"amountOfHashtagFollowers": 0
}
],
"comments": []
}
However my map function doesn't seem to put the data inside the <h2> tag. Setting the breakpoint exactly at this.state.post.map(x => <h2>{x.title}</h2>) showed that it's not even being hit, however there are no errors on the console. How can I properly map this?
EDIT:
Just like Alon Barenboim and Nicholas Tower pointed out, there was a typo (setting state to posts instead of post). However, the issue now is that that piece of code now throws the error that this.state.post.map is not a function.

I guess that the issue is that you call this.setState({ posts: res.data }) for posts, but trying to map post: this.state.post.map(x => <h2>{x.title}</h2>
EDIT:
If I understand correctly, you just need to display the data that is stored inside the posts object in a component.
In order to do so, you can just do the following:
const api = axios.create({
baseURL: `https://localhost:5001/api/v1/`
})
class SinglePost extends Component {
state = {
posts: []
}
constructor(props) {
super(props);
api.get(`posts/9a6b5be6-b3ef-4c2d-a36b-08da14c62914`).then(res => {
console.log(res.data)
this.setState({ posts: res.data })
})
}
render() {
return (
<div>
{this.state.posts ? <h2>this.state.posts.title</h2> : null}
</div>
)
}
}
export default SinglePost;
You can obtain each value that is stored inside the state object just by writing {this.state.posts.ATTRIBUTE}

Related

I keep getting this TypeError: Cannot read property 'map' of undefined

I am trying to render this data from the API onto my page.
I am aware that what I am trying to render isn't in the typical array its a javascript object which is throwing me off.
With my code, as is, the API data is there its just a matter of being able to access because .map isn't working because the data I have is not an array.
Do I need to create another function?
I am not sure
import React from 'react';
import './App.css';
class App extends React.Component {
state = {
apiData: []
}
render() {
console.log('api datat is')
return (
<div>
<center>
<h1>hello something</h1></center>
{this.state.apiData.map(title => title.rendered)}
</div>
)
}
componentDidMount() {
fetch('http://www.mocky.io/v2/5dece3d333000052002b9037')
.then(response => response.json())
.then(data => {
this.setState({
apiData: data.data
})
})
console.log("component fetched data")
}
}
export default App
Any help would be great.
I am still learning how to code so be nice
Look at the API response:
{
"id": 233383,
"date": "2019-10-28T10:50:53",
"date_gmt": "2019-10-28T10:50:53",
"modified": "2019-10-28T10:55:14",
"modified_gmt": "2019-10-28T10:55:14",
"link": "https:\/\/www.stylist.co.uk\/long-reads\/friendship-friends-whatsapp-facebook-messenger-social-media-group-chat-best-friends-psychology-advice\/233383",
"title": {
"rendered": "Whatsapp and Facebook Messenger: how group chat is changing the dynamic of our friendships"
},
"slug": "whatsapp-and-facebook-messenger-how-group-chat-is-changing-the-dynamic-of-our-friendships",
etc.
It does not have a .data property, so
.then(data => {
this.setState({
apiData: data.data
})
})
sets undefined to this.state.apiData.
The only rendered exists in the one top-level object in the response, so you should remove the .map entirely, something like:
.then(data => {
this.setState({
apiData: data
})
})
<h1>hello something</h1></center>
{this.state.apiData.title.rendered}

Data is not Rendering to the Screen in React Project

I have a react application that I am trying to render some basic JSON to the screen. I will place a small snippet of the JSON object below. I am trying to represent the data in the researchPage. I will be placing the entire component class below. Please let me know if I can provide any further information to clarify.
db.json - This is the JSON file that I am trying to pull data from.
{
"hits": [
{
"_index": "issflightplan",
"_type": "issflightplan",
"_key": "IDP-ISSFLIGHTPLAN-0000000000000447",
"_version": 1,
"_score": null,
"ContentType": {
"__deferred": {
"uri": "https://bi.sp.iss.nasa.gov/Sites/FP/_api/Web/Lists(guid'a9421c5b-186a-4b14-b7b2-4b88ee8fab95')/Items(252)/ContentType"
}
researchPage - This is the component page that I am trying to render the JSON data too. I have looked at the console and do not seem to be getting any errors. The page is showing, and the H1 Record MetaData is also rendering to the screen, however, there is no H3 or Paragraph below it.
import React, { Component } from "react";
import ReactDOM from "react-dom";
import data from "../../data/db.json";
console.log(data);
class ResearchPage extends Component {
constructor(props) {
super(props);
this.state = {
isLoading: false,
error: null,
dataSet: [],
data: [],
}
}
componentDidMount() {
this.setState({ isLoading: true });
fetch(data)
.then((res) => {
debugger;
if (res.ok) {
return res.json();
} else {
throw Error("Error Fetching Data");
}
})
.then((data) => {
console.log(data);
this.setState({ data: data, isLoading: false });
})
.catch((error) => {
console.log((error) => this.setState({ error }));
});
}
render() {
const { error, isLoading, data } = this.state;
const dataItems = data.map((dataSet) => (
<div key={dataSet.id}>
<h3>{dataSet._index}</h3>
<p>{dataSet.uri}</p>
</div>
));
if (error) {
return <p style={{ color: "red" }}>{error.message}</p>;
}
if (!isLoading) {
return <p>Loading Data...</p>;
} else
return (
<div>
<h1>Record MetaData</h1>
{dataItems}
</div>
);
}
}
export default ResearchPage;
Update:
I got it working.
The problem I was facing was when trying to display the data was that the data in the render method wasn't initialized but, if you do the mapping inside the didmount and also it looks like your trying to access the list so you need to specify that.
const hits = data.hits.map((dataSet) => (
<div key={dataSet.id}>
<h3>{dataSet._index}</h3>
<p>{dataSet.uri}</p>
</div>
));
this.setState({ data: hits, isLoading: false });
render() {
const { error, isLoading, data } = this.state;
if (error) {
return <p style={{ color: "red" }}>{error.message}</p>;
}
if (isLoading) {
return <p>Loading Data...</p>;
} else {
return (
<div>
<h1>Record MetaData</h1>
{data}
</div>
);
}
}
Oh also, I put the db.json in the public directory and accessed it as follows because I was getting module not found.
fetch("db.json", {
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
}})
.then (...)
How to troubleshooting
To feed some example test data to see if it's and IO issue you can convert the json object manually into a javascript object like so,
this.setState({ data: [
{ _index: 'ia', uri: 'ub' },
{ _index: 'ib', uri: 'ub' },
], isLoading: false });
More Explanation
Issues you faced
Component Lifecycle - The ComponentDidMount will fire after or at the same time that render fires. This raised a concurrency issue where the data you were trying to map was not use was not yet available. By doing the map inside the DidMount ensures that it's performed only once the data is ready. In the meantime the {data} in the render will be null and nothing will be displayed. If you wanted to test this you could put a sleep/block inside your didmount and see that the data won't display until the block is complete.
Correctly navigating the json object - Using data.map instead of data.hits.map was targeting the entire json object and not the specific list "hits" that actually needed to be accessed (data.hits). Next the .map function iterates through that list providing 'dataSet' on each iteration which allow the use that value.
Potential - I don't normally get data from a json file but, instead from web API's so, I have little experience but, normally to access the file you need a special library or have to put the file inside your public folder which is available throughout the project. You can test this by doing http://localhost:3000/db.json
if (!isLoading) {
return <p>Loading Data...</p>;
This is backwards. It should be:
if (isLoading) {
return <p>Loading Data...</p>;

Reactjs Component lifecycle - Fetching data from user input

I'm new to Reactjs. I try to build a filters system from a search result.
When user select a filter I would like get new data using an AJAX call according the chosen filter.
I set an eventHandler function on filter's checkbox which set state of my component. This make React re-render the component. But at this point, there is no newest data.
componentWillUpdate() seems perfect for this purpose but it will be deprecated on next release (17) unless using UNSAFE_componentWillUpdate().
How fetch newest data before re-rendering the component? Which lifecycle method is better choice?
First set of data is set on my index.html file
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Reactjs - Filtres component</title>
</head>
<body>
<div id="filtres" class="filtres" style="width:250px;"></div>
<script type="text/javascript">
var filtres = [{
"date": {
"libelle": "Year",
"buckets": {
"date2018": {
"name": "date[0]",
"value": "2018",
"title": 2018,
"libelle": 2018,
"nb": 1
},
"date2016": {
"name": "date[1]",
"value": "2016",
"title": 2016,
"libelle": 2016,
"nb": 54
},
"date2015": {
"name": "date[2]",
"value": "2015",
"title": 2015,
"libelle": 2015,
"nb": 70
}
}
}
},
{
// some filters
}
}];
</script>
<script src="dist/filtresComponent.js"></script>
</body>
</html>
FiltresComponent.js
import React from 'react'
import ReactDOM from 'react-dom'
import Filtres from './Filtres'
ReactDOM.render(<Filtres filtres={filtres} />, document.getElementById('filtres'));
Filtres.js
import React, { Component } from 'react'
import './Filtres.css'
import Crit from './Crit'
class Filtres extends Component {
constructor(props) {
super(props);
this.state = {
filtres : this.props.filtres,
appliedFiltres : {}
}
}
addFiltre = (filtreName, filtreValue) => {
console.log('addFiltre from FiltresComponent !!');
console.log('We add : ');
console.log('filtreName :' + filtreName + ' ,value : ' + filtreValue);
this.setState((state) => {
return {appliedFiltres: Object.assign(state.appliedFiltres, {[filtreName]: filtreValue})}
});
console.log(this.state);
}
// before re-rendering sounds good but will be deprecated on reactjs 17
componentWillUpdate = () => {
console.log('componentWillUpdate');
// Fetching before rendering ?
}
render() {
console.log('render Filtres.js');
return ([
this.state.filtres.map((crit, index) => {
let libelle = crit[Object.keys(crit)].libelle;
let critValues = Object.values(crit[Object.keys(crit)].buckets);
return <Crit key={index} libelle={libelle} critValues={critValues} addFiltre={this.addFiltre}/>
})
])
}
}
export default Filtres
Crit.js
import React, { Component } from 'react'
class Crit extends Component {
static defaultProps = {
open : true
}
constructor(props) {
super(props);
this.state = {
open : this.props.open
}
}
showHideCrit = (e) => {
if(this.state.open){
e.target.nextElementSibling.style.display = 'none';
this.setState({ open: false });
}else{
e.target.nextElementSibling.style.display = 'flex';
this.setState({ open: true });
}
}
addFiltre = (e) => {
console.log('addFiltre from CritComponent !!');
// User chosen filter
let filtreName = e.target.name;
let filtreValue = e.target.value;
// LiftUp
this.props.addFiltre(filtreName, filtreValue);
}
render() {
console.log('render Crit.js');
return ([
<div className="crit">
<a className={"js-head_crit " + (this.state.open ? 'open' : '' )+""} onClick={this.showHideCrit}>{this.props.libelle}</a>
<div className="crit-values">
{
this.props.critValues.map((critValue,index) => {
return (
<div key={index} className="crit-value" data-count={critValue.nb}>
<input type="checkbox" name={critValue.name} value={critValue.value} onChange={this.addFiltre}/>
<label className="crit-libelle">
{critValue.libelle}
<span className="crit-nb">{critValue.nb}</span>
</label>
</div>
)
})
}
</div>
</div>
])
}
}
export default Crit
Output
Here user want to filter by year : 2015
I add date[2] : 2015 to appliedFiltres using this.setState
Then I want to fetch data using the filter year = 2015
Finally re-render component with new values
A possible solution would be to trigger the fetch operation immediately after setting the new filters in the state, you can do this via a call back to this.setState method call:
addFiltre = (filtreName, filtreValue) => {
// ...other codes
this.setState(
{}, // state update value
this.fetchData() // The function to trigger the AJAX call
);
}
Note: The re-render should happen regardless of the triggered AJAX call, you shouldn't wait for the AJAX call to complete before doing the render because this would make you app un-usable(and miserable) for a while(until the AJAX call completes).
To ensure a good user experience and also avoid showing stale data, you can display maybe a loading component(which can be as simple as a text: loading...) while the fetch from sever is happening, for that you would need a state value to track the AJAX process.
In the code block below, I added a fetchingData boolean to the state, you can use that to track when the data fetching is happening and when it is done.
class Filtres extends Component {
constructor(props) {
// ...other codes
this.state = {
// ...other states
fetchingData: false,
};
}
// ...other methods
fetchData() {
// set fetchingData to true as soon as the AJAX process starts
this.setState({ fetchingData: true });
// make ajax call with set filters
// when ajax call is done, set it back to false
this.setState({ fetchingData: false });
}
render() {
if(this.state.fetchingData) {
// display loading component
} else {
// display the current data
}
// ...other methods
};

React set-state doesn't update in fetch success function (on key up event)

I have a search component containing an input on which I defined a key up event handler function for fetching data based on entered string. As you can see below:
class SearchBox extends Component {
constructor(props) {
super(props);
this.state = {
timeout: 0,
query: "",
response: "",
error: ""
}
this.doneTypingSearch = this.doneTypingSearch.bind(this);
}
doneTypingSearch(evt){
var query = evt.target.value;
if(this.state.timeout) clearTimeout(this.state.timeout);
this.state.timeout = setTimeout(() => {
fetch('https://jsonplaceholder.typicode.com/todos/1/?name=query' , {
method: "GET"
})
.then( response => response.json() )
.then(function(json) {
console.log(json,"successss")
//Object { userId: 1, id: 1, title: "delectus aut autem", completed: false } successss
this.setState({
query: query,
response: json
})
console.log(this.state.query , "statesssAfter" )
}.bind(this))
.catch(function(error){
this.setState({
error: error
})
});
}, 1000);
}
render() {
return (
<div>
<input type="text" onKeyUp={evt => this.doneTypingSearch(evt)} />
<InstantSearchResult data={this.state.response} />
</div>
);
}
}
export default SearchBox;
The problem is the setState which I used in the second .then(). The response won't update . I want to update it and pass it to the InstantSearchResult component which is imported here. Do you have any idea where the problem is ?
Edit - Add InstantSearchResult component
class InstantSearchBox extends Component {
constructor(props) {
super(props);
this.state = {
magicData: ""
}
}
// Both methods tried but got error => Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.
componentDidUpdate(props) {
this.setState({
magicData: this.props.data
})
}
shouldComponentUpdate(props) {
this.setState({
magicData: this.props.data
})
}
render() {
return (
<h1>{ this.state.magicData}</h1>
);
}
}
export default InstantSearchBox;
Edit:
Be aware that setState is asynchronous reading this article.
I've understand that the setState works fine in my fetch success the problem was the console.log which I shouldn't use it after setState instead I console.log in render() and I found out that the state updates correctly .
The other thing I wasn't careful for was the InstantSearchResult Constructor! When I re-render the SearchBox component consequently the InstantSearchResult renders each time but it's constructor runs just once. And if I use setState in InstantSearchResult I will face an infinite loop so I have to use this.props instead to pass the data to the second component.
this has been overridden inside the promise callback function. You to save it to a variable:
doneTypingSearch(evt){
var _this = this,
query = evt.target.value;
if(this.state.timeout) clearTimeout(this.state.timeout);
this.state.timeout = setTimeout(() => {
fetch('https://jsonplaceholder.typicode.com/todos/1/?name=query' , {
method: "GET"
})
.then( response => response.json() )
.then(function(json) {
console.log(json,"successss")
//Object { userId: 1, id: 1, title: "delectus aut autem", completed: false } successss
_this.setState({
query: query,
response: json
})
console.log(_this.state.query , "statesssAfter" )
}/*.bind(this)*/)
.catch(function(error){
_this.setState({
error: error
})
});
}, 1000);
}

Working with arrays of objects as props in React

I'm building a small app that consumes a REST api. I'm running into problems displaying information inside arrays of objects, see code below:
actions.js
import axios from 'axios'
function fetchService () {
return axios.get('http://localhost:5000/ldbws-rest-proxy/v0.1/departure-board/IPS')
.then(function (response) {
return {
service: response.data.trainServices[0]
}
})
.catch(function (response) {
console.log(response)
})
}
export default fetchService
train_service.js
import fetchService from '../actions'
import DepartureTime from './departure_time'
import OriginStation from './origin_station'
var TrainService = React.createClass ({
getInitialState () {
return {
service: []
}
},
componentDidMount () {
fetchService()
.then(function (dataObj) {
this.setState({
service: dataObj.service
})
}.bind(this))
},
render () {
return (
<section>
<DepartureTime time={this.state.service.std} />
<OriginStation name={this.state.service.origin[0].crs} />
</section>
)
}
})
export default TrainService
JSON sample (response.data.trainServices[0])
{
"destination": [
{
"crs": "CBG",
"locationName": "Cambridge"
}
],
"etd": "On time",
"operator": "Greater Anglia",
"operatorCode": "LE",
"origin": [
{
"crs": "IPS",
"locationName": "Ipswich"
}
],
"serviceID": "ILZn7gyLj+eoZZfyaFlP0w==",
"std": "12:20"
}
The problem is that <OriginStation name={this.state.service.origin[0].crs} /> throws an error:
TypeError: undefined is not an object (evaluating 'this.state.service.origin')
I'm not sure why this isn't working, if I do console.log(dataObj.service.origin[0].crs) inside componentDidMount it outputs fine. I think it's something to do with the origin array...
Any help appreciated.
EDIT:
Screenshot of the state in the Chrome Inspector:
It's because your TrainService render method calls earlier than fetchService promise resolves.
Easiest way to fix your error is wait for fetchService updates service state:
var TrainService = React.createClass ({
getInitialState () {
return {
service: null
}
},
componentDidMount () {
fetchService()
.then(function (dataObj) {
this.setState({
service: dataObj.service
})
}.bind(this))
},
render () {
if (this.state.service === null)
return null;
return (
<section>
<DepartureTime time={this.state.service.std} />
<OriginStation name={this.state.service.origin[0].crs} />
</section>
)
}
})
fetchService is making an async call. So when componentDidMount is run, it will make an async call and proceeds to render.
When the render function is executed for the first time you state is not populated with the data and it has an empty array for this.state.service, from getInitialState.
If you are writing console.log inside componentDidMount as
componentDidMount () {
fetchService()
.then(function (dataObj) {
console.log(dataObj.service.origin[0].crs)
this.setState({
service: dataObj.service
})
}.bind(this))
},
the console.log gets executed only when the async call has succeeded, so the data is available.
To solve this either don't render the component until the state data is ready.
render() {
this.state.service.length == 0 && return null
...
}

Categories