Results are not populating after fetching from API using React - reactjs

I am working with some examples to fetch the data from an API using fetch. But, it is returning nothing in view. What i am doing wrong? Why it is not populating? Here is a link to the code.
Application code here:
import React, { Component } from 'react';
import { render } from 'react-dom';
import Hello from './Hello';
import axios from 'axios';
import './style.css';
const url='https://10degrees.uk/wp-json/wp/v2/posts';
class App extends Component {
constructor() {
super();
this.state = {
name: 'React',
searchInput: 'tirur',
avatarDatas: []
};
this.fetchData = this.fetchData.bind(this);
}
componentDidMount(){
this.fetchData(url);
}
fetchData = (apiToFetch)=>{
fetch(apiToFetch)
.then(result=>result.json())
.then(avatarData=>{
this.setState({
avatarDatas : avatarData
})
})
.catch((error) => console.log(error));
}
render() {
console.log(this.state.avatarDatas)
return (
<div>
<Hello name={this.state.name} />
<p>
{this.state.avatarDatas.map(item=>{
<div>{item}</div>
})}
</p>
</div>
);
}
}
render(<App />, document.getElementById('root'));

Your code is fetching data from the remote URL correctly. However, you're using the map function in your render method wrong. You have:
{this.state.avatarDatas.map(item=>{
<div>{item}</div>
})}
This does indeed map through all the entries in avatarDatas, but the function the callback you've provided the map isn't returning anything. You've written a function block with a JSX statement in it, and no return statement. What you should have written is:
{this.state.avatarDatas.map(item=>{
return (<div>{item}</div>);
})}
or even this, where there isn't an actual function block but just a return value:
{this.state.avatarDatas.map(item=>
<div>{item}</div>
)}
At this point, the avatarDatas still won't be listed, because the JSX in your map callback is trying to have item rendered. item is an object, and React won't know how to convert it to a string. Rather do this:
{this.state.avatarDatas.map(item=>
<div>{item.title.rendered}</div>
)}
(Or any other of the many fields that each avatarData has.)
Finally, React may still issue a warning, saying that each item in a list must have a unique key. Whenever you use map to create a number of elements, React expects you to give a unique identifier to each element, which it will use to identify that element when it needs to update your list during rerendering. That means that you should do this:
{this.state.avatarDatas.map((item, index) =>
<div key={index}>{item.title.rendered}</div>
)}
Now, your map callback assigns an index to each <div> and all is well with the world.

Related

Convert React Class to a Functional Component

On my older repository, I have used Class Components with the following CruiseListHeader component code, as an example (used for showing Cruise Buttons).
import React from 'react';
import {getCruiseLines} from '../api/api'
import ListofShips from './ListofShips'
class CruiseListHeader extends React.Component {
constructor(props) {
super(props)
//setting intial state for Cruise Headings and initialize cruiseHeaders as an empty array
this.state = {
cruiseHeaders: []
}
//binding methods for setting up Cruise Line Headings
this.setUpCruiseLines = this.setUpCruiseLines.bind(this)
}
componentDidMount() {
console.log('cdm')
this.setUpCruiseLines()
}
setUpCruiseLines() {
console.log('getcruiselines')
getCruiseLines()
.then(res => {
this.setState({
cruiseHeaders: res
})
})
}
render() {
return (
<React.Fragment>
{/* There will be Headings for all the Cruise Lines. */}
{/* Map the Cruiseline Headings for each Ship to display them on the page. I want to map ship, because I need each ship displayed in a List, when Cruise Line Heading is clicked. */}
<div className = "cruiseContainer">
{this.state.cruiseHeaders.map (ship => {
return (
<div key={ship.cruise_line}>
{/* ListofShips component needs cruise_line, because when user clicks on Cruise Line Heading button,
we need to fetch ships that belongs to that particular cruiseline. */}
{/* We need to render multiple ListofShips components, with one for each cruiseline */}
<ListofShips cruise_line={ship.cruise_line}></ListofShips>
</div>
)
})}
</div>
</React.Fragment>
)
}
}
export default CruiseListHeader
Please note that this is all related to a Cruise Lines Page shown below, that has a main CruiseLines.jsx component with the CruiselistHeader.jsx mentioned above imported into it.
Cruise Buttons on Cruise lines Page
Now I want to start the change by converting this React Class Component into a Functional one.
This is what I have for my CruiseListHeader as a Functional Component, so far.
Please note that ListofShips is now called ShipsList in this new repository.
import React, { useEffect, useState} from 'react'
import {getCruiseLines } from '../api/api'
import ShipsList from './ShipsList'
function CruiseListHeader() {
// Declare cruiseHeaders State variable
const [cruiseHeaders] = useState({
})
useEffect (() => {
// Note: This was the original ComponentDidMount that took Binding this.setUpCruiseLines()
// Now it is coming from the CruiseListHeader.js useEffect to the DOM
}
)
return (
<>
<div>
<div key={ship.cruise_line}>
<ShipsList cruise_line={ShipsList.cruise_line}></ShipsList>
</div>
</div>
</>
)
}
export default CruiseListHeader
What I am wanting to understand, is how does the Functional Component handle the state from my api, the binding and the mapping that I was previously using in my Class Component ?
If anyone has any ideas of how I can handle this, then that would be of great valuable help thanks.
You're ignoring the setter for the state, your useState line should be:
const [cruiseHeaders, setCruiseHeaders] = useState({});
Then you'd use that setCruiseHeaders function to set the state:
useEffect (() => {
getCruiseLines()
.then(res => {
setCruiseHeaders({
cruiseHeaders: res
})
});
}, []); // Make sure to also pass an array here, or you'll be triggering this effect on every render
As an aside, you probably meant to initialize your state value to an array instead of an object:
const [cruiseHeaders, setCruiseHeaders] = useState([]);
Since the "cruise headers" data in your original code was an array.
import React, { useEffect, useState} from 'react'
import {getCruiseLines } from '../api/api'
import ShipsList from './ShipsList'
function CruiseListHeader() {
// Declare cruiseHeaders variable and set it's array using useState
const [cruiseHeaders, setCruiseHeaders] = useState([]);
// Note: This was the original ComponentDidMount that took Binding this.setUpCruiseLines()
// Now it is coming from CruiseListHeader.jsx useEffect to the DOM
useEffect (() => {
getCruiseLines()
.then(res => {
setCruiseHeaders({
cruiseHeaders: res
})
});
}, []); // Make sure to also pass an array here, or you'll be triggering this effect on every render
return (
<>
{/* <div className = "cruiseContainer"> I don't think I need this because I am using Tailwind CSS*/}
{/* When I use Sass in my next final repo I may not need a div either */}
{cruiseHeaders.map (ship => {
// return ( Do not need return here, I think
<div key = {ship.cruise_line}>
{/* ListofShips component needs cruise_line, because when user clicks on
Cruise Line Heading button, we need to fetch ships that belongs to that particular
cruiseline. */}
{/* We need to render multiple ShipsList components, with one for each cruiseline */}
<ShipsList cruise_line={ship.cruise_line}/>
{/* </ShipsList> I think I don't I need this closing tag*/}
</div>
// ) I think, I do not need return closing bracket here
})}
{/* </div> I think, I do not need closing div from cruiseContainer here*/}
</>
)
}
export default CruiseListHeader

React array update

Working on reactJS project, and I know we don't mutate the state (usually i don't but don't know the exact reason) so I just tried some wrong approach even its working correctly.
import logo from './logo.svg';
import './App.css';
import Test from './test';
import {Home, NotFoundComponent,Contact, Profile} from './test/home'
import {
BrowserRouter as Router,
Route,
Link
} from 'react-router-dom'
import React, {Component} from 'react';
class App extends Component {
constructor(props) {
super(props);
this.state = {
list: [42, 33, 68],
};
}
onUpdateItem = i => {
this.state.list[i]++;
this.setState({
list: this.state.list,
})
// this.setState(state => {
// const list = state.list.map((item, j) => {
// if (j === i) {
// return item + 1;
// } else {
// return item;
// }
// });
// return {
// list,
// };
// });
};
render() {
return (
<div>
<ul>
{this.state.list.map((item, index) => (
<li key={item}>
The person is {item} years old.
<button
type="button"
onClick={() => this.onUpdateItem(index)}
>
Make me one year older
</button>
</li>
))}
</ul>
</div>
);
}
}
export default App;
this.state.list[i]++;
this.setState({
list: this.state.list,
})
what is the problem with when I update the code above instead map method, both give the correct output?
explain to me this behind the scene
code sandbox link
Here is the reason:
When you mutate an array, say list[i]++ the identity of the array is not changed so listBefore ==== listAfter, while you map over the array listBefore !== listAfter, when you invoke setState, ALL the children component will be invoked again by default even if you put nothing inside setState. Because react can not tell if the children update is necessary by default and it is a waste of resources actually.
You can prevent this from happening by telling the Children "Do NOT update" by calling shouldComponentUpdate or simply use PureComponent.
And the way to tell if updates are necessary is to compare using ===
In addition, Even if you do not care about performance. you should never do this. It will cause some debuggable bugs in some cases mainly because the updates in react is NOT synchronized. I never encounter by the way because I do not even dare to try
When you update your state you should take care of immutable.
onUpdateItem = i => {
//this.state.list[i]++;
//this.setState({
// list: this.state.list,
//})
this.setState({
list: this.state.list.map((value,index)=>{
if(i === index) return value+1;
return value;
})
});
}
If you are not maintaining immutable, your app will not work what you expected.
React states are updated follow life-cycle.
if without immutability, it means you are breaking the life-cycle.

Need help to fetch json data with Axios dependancy for react

I need help using the axios dependancy on react.
I'm fetching data from this endpoint: https://api.covid19api.com/summary
I setup a config for my API (config.jsx)
import axios from 'axios';
export default axios.create({
baseURL: `https://api.covid19api.com/summary`,
responseType: "json"
});
then i call it in my App, (a class component) as API
import API from './config.jsx';
and execute it within my ComponentDidMount()
componentDidMount() {
API.get().then(res => {
const countries = JSON.stringify(res.data.Countries);
//console.log(countries);
this.setState({ covid: countries });
console.log(`Etat du state: ${this.state.covid}`);
})
}
I get the data, store it in my state named 'covid', but when it comes to map over the results i get an error "TypeError Cannot read property 'map' of null" I think i have to convert the data into an array but i don't know how to do this .
render() {
return (
<div className="App">
<header className="App-header">
<h1>{this.state.appliname}</h1>
{this.state.covid.map(item => (
<div>{item.Country}</div>
))}
</header>
</div>
);
}
Here's the full script on codesandbox: https://codesandbox.io/s/intelligent-faraday-ykewv?file=/src/App.js
Thanks
There are several things you need to consider:
Always handle errors in promises. Sometimes you may face API failure, so you should handle the API request gets failed that we should do. So simply add a catch handler to your promise chain.
You should always handle first data initiation. In the first render of your page, there is no this.state.covid so you can't pass it to your view and map through it, so if you do this it will throw an error. To make this work you should add conditional rendering to your element.
Define your first state initiation correctly. Since you expect your this.state.covid to be an array, so you should define it as an array in the first place (this.state = {covid: []}).
Avoid passing strings to Array#map. When you try to convert your incoming items from API to JSON with JSON.stringify(res.data.Countries) it will make your data as JSON. Since JSON comes with string type it won't fit array helpers like Array#map, when you got your data there is no need to make JSON of the. If in any case, your incoming data was JSON itself you should parse it with JSON.parse().
Working demo:
Set init state default array
this.state = { covid: [], appliname: "CovidFacts" };
Set countries
const countries = res.data.Countries;
Follow all :
import React from "react";
import "./App.css";
import API from "./config.jsx";
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
covid: [],
appliname: "CovidFacts"
};
}
componentDidMount() {
API.get().then(res => {
const countries = res.data.Countries;
this.setState({ covid: countries });
console.log(`Etat du state: ${this.state.covid}`);
});
}
render() {
return (
<div className="App">
<header className="App-header">
<h1>{this.state.appliname}</h1>
{this.state.covid.map(item => (
<div>{item.Country}</div>
))}
</header>
</div>
);
}
}
export default App;

How to print a list of objects from firebase in react?

I am getting the snapshot from the firebase and setting in the setState method and the in the render method i just want to print the list. In the first place because firebase is returning an object the map function was not working and i found something Array.from(this.state.testList) which seems to work but nothing is displayed. I am attaching the code and my firebase tree so you can see what I am trying to do.
25-10-2019:
firstPoint:
newsDesc: "Chris is promoted to scrum master!"
newsTitle: "Chris is promoted!!"
secondPoint:
newsDesc: "Christodoulos needed a change in his life"
newsTitle: "Christodoulos is leaving"
In the code above i am getting the snapshot of 'kabalaNews' which will return me and object with the data and this object will have many other objects with the points and the each point has a title and a description.
import React, { Component } from 'react'
import firebase from 'firebase';
import {DB_CONFIG} from './Config';
export class printFirebase extends Component {
constructor() {
super()
this.app = firebase.initializeApp(DB_CONFIG);
this.database=this.app.database().ref().child('kabalaNews');
this.state = {
testList: []
}
}
componentDidMount(){
this.database.on('value' , snap=> {
this.setState({
testList:snap.val()
});
});
}
render() {
console.log(this.state.testList);
return (
<div>
<div>
{Array.from(this.state.testList).map(news =>
<p>{news.25-10-2019.newsDesc} {news.25-10-2019.newsTitle}
</p>)}
</div>
</div>
)
}
}
export default printFirebase
You have a few mistakes:
I'd actually state the entire snapshot into the state, instead of just the values. This ensures you can meet two needs that you're bound to have in the future:
The items will show up in the order in which you queried them, instead of in alphabetical order of their keys.
You have access to the ID of each item, which is useful once you want to start manipulating the items.
You're using an invalid syntax for accessing properties here: news.25-10-2019.newsDesc. A JavaScript identifier cannot contain a -, so you'll need to use [] notation to access the value: news["25-10-2019"].newsDesc.
So in total that becomes:
componentDidMount(){
this.database.on('value' , snap=> {
this.setState({
testList:snap
});
});
}
render() {
console.log(this.state.testList);
return (
<div>
<div>
{this.state.testList.forEach(news =>
<p>{news.val["25-10-2019"].newsDesc} {news.val["25-10-2019"].newsTitle}
</p>)}
</div>
</div>
)
}
}

How to log exackly one object from fetched json data in react?

Is there a way to log 1 object or 2 or as much as i want to be logged in console?
Im using simple data from jsonplaceholder.typicode.com (quite usefull for learning purpose) which every of object has unique id.
For example:
I fetched data and rendered 200 posts on website, ok... but if i have a data which contains 100 logos or banners or layouts for website i want to render a specific logo or banner with unique id 30.
So, how can I render only 1st, 2nd, 3rd or x post/posts from 200?
This is what i have now:
App.js
import React, { Component } from 'react';
import './css/App.css';
import './css/bootstrap.css';
import $ from 'jquery';
import Todos from './Todos';
class App extends Component {
constructor(props) {
super(props);
this.state={
todos:[],
};
}
getTodos() {
$.ajax({
url: 'https://jsonplaceholder.typicode.com/todos',
dataType: 'json',
cache: false,
success: function (data) {
this.setState({todos: data}, function () {
console.log(this.state);
});
}.bind(this),
error: function (xhr, status, err) {
console.log(err);
}
})
}
componentWillMount(){
this.getTodos();
}
componentDidMount() {
this.getTodos();
}
render() {
return (
<div>
<Todos todos={this.state.todos}/>
</div>
);
}
}
export default App;
Todos.js
import React, {Component} from 'react';
import TodoItem from './TodoItem';
class Todos extends Component {
render() {
let todoItems;
if(this.props.todos) {
todoItems = this.props.todos.map(todo => {
return (
<TodoItem key={todo.title} todo = {todo} />
);
});
}
return (
<div>
<h2>Todo list from api</h2>
{todoItems}
</div>
);
}
}
Todos.propTypes = {
todos: React.PropTypes.array
};
export default Todos;
TodoItem.js
import React, {Component} from 'react';
class TodoItem extends Component {
render() {
return (
<li>
<strong>{this.props.todo.title}</strong>
</li>
);
}
}
TodoItem.propTypes = {
todo: React.PropTypes.object
};
export default TodoItem;
#edit
Is there possible to filter mapped json data and find object depending on id and then render this object?
Slicing multiple items
You can use another state property, like filterCount, which can be either set by you manually, or you can trigger setState events from buttons e.g.
constructor(props) {
super(props);
this.setFilter = this.setFilter.bind(this);
this.state={
todos: [],
filterCount: 20 // Default value
};
}
setFilter(count) {
this.setState({ filterCount: count });
}
render() {
const { todos, filterCount } = this.state;
return(
<div>
...
<button onClick={this.setFilter(10)} />
<button onClick={this.setFilter(20)} />
<button onClick={this.setFilter(50)} />
</div>
)
}
This will ensure that your component is rerendered each time when you change the count.
Now the second part, filtering the first x items.
The main way is to use Array.prototype.slice
Slice example:
render() {
const { todos, filterCount } = this.state;
const filteredToDos = todos.slice(0,filterCount);
return(
<div>
...
{ filteredToDos.map(todo => {
return (
<TodoItem key={todo.title} todo={todo} />
);
}) }
</div>
)
}
Make sure you don't accidentally use splice instead of slice, because splice doesn't do the copy to a new array, but modifies the original one.
This example can be easily modified to support paging aswell. You could add a currentPage parameter to state, and just modify the slice line to slice the array based on which page you are.
Also, think about hard whether you need to use jQuery in your app. If you are using it only for convenience, then you are making a big mistake, as it is a hefty library that increases your bundle size considerably. Try to learn doing things the react way :)
Your ajax request can be done by using fetch api.
return fetch("https://jsonplaceholder.typicode.com/todos")
.then(response => response.json())
.catch((error) => {
console.error(error);
});
If you don't need out of the box support for the older browsers, fetch api will be fine for last 2-3 major versions of modern browsers, including mobile ones. It can be polyfilled for older ones such as IE8 aswell.
Also, there are libraries like axios that are actually much smaller then jQuery if you need more options and support for all request types and headers.
Also, the fetch action itself, could be decoupled from the component to a separate actions.js file so it can be imported / used from multiple components in the future, or refactored more easily to support working with something like Redux if your app grows.
Getting a single item
Fetching single item from API directly - suggested
If we are talking about performance, then the best way is to get a single item directly from API. Considering that this is a REST based api then the way would be:
https://jsonplaceholder.typicode.com/todos/{id}
Rewriten for a fetch example with a template literal:
return fetch(`https://jsonplaceholder.typicode.com/todos/${itemId}`)
.then(response => response.json())
.catch((error) => {
console.error(error);
});
This should also return a single object, not an array, so you should be able to pass it as a prop and use it immediately. IMHO, this is the best solution to the problem.
Fetching all items from API, then filtering with filter
Second option is to get all layouts, then filter the result which you can see in the example of: #mayank-shukla
Bottleneck of this method is that Array.prototype.filter can return multiple results.
Fetching all items from API, then filtering with find
Third option is to get all layouts, then filter the result with Array.prototype.find
Example is the same as filter, just change the keyword to find.
Find will return the first result in case of multiple ones.
Fetching all items from API, then filtering by index
Fourth option is getting all layouts, then filtering by index, e.g.
todos[itemId-1]
This will also work, but, you need to be 100% certain that backend will order the results in the array by their id. Also, index starts from 0, and your ids from 1, so this can be a source of major confusion.
Instead of passing all the data to child component filter out the data on the basis of condition and pass that filtered data to child component.
Like this:
render() {
//filter data of these ids
let ids = [1,2,3,4];
//filtered data
let data = this.state.todos.filter(el => ids.indexOf(el.id) != -1);
return (
<div>
<Todos todos={data}/>
</div>
);
}
If you want to pass first x data, the use #array.slice and pass the result, like this:
render() {
//filtered data
let data = this.state.todos.slice(0, 20);
return (
<div>
<Todos todos={data}/>
</div>
);
}
Instead of putting the filter part inside render you can do that after getting the response.

Resources