Render a simple list in React with promises - reactjs

I'm trying to render some content from a service which returns a promise, but I can't figure out how I should return the data in my "return" block.
It's no problem to return some static data, as shown in this example. But how can I return the data from the commented code in the same way?
The image shows the console, where you can see the promise.
I know the answer is probaly very simple, but I just can't wrap my head around it.
render() {
const data2 = [
{title:'item one', id:'000'},
{title:'item two', id:'001'}
];
console.log(data2);
const data = firebaseCon.content.get('text', { fields: ['id', 'title'] });
console.log(data);
var itemList = data2.map(function(item) {
return <li className="item" key={item.id}>{item.title}</li>;
});
return (
<ul>
{itemList}
</ul>
)
}

First of all, I would say that you are doing in wrong way.
You should never make a server request in render function.
You should make a server request in componentWillMount() or componentDidMount().
If you know async/await concept then it would be helpful for you.
You can check this link..
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function
You can try like this one...
import React, { Component } from 'react';
export default class ListData extends Component {
constructor(props) {
super(props);
this.state = {
data: []
};
}
async componentDidMount() {
const data = await firebaseCon.content.get('text', { fields: ['id', 'title'] });
this.setState({ data });
}
render() {
const { data } = this.state;
const itemList = data.map(function(item) {
return <li className="item" key={item.id}>{item.title}</li>;
});
return (
<div>
{data.length > 0 &&
<ul>
{ itemList }
</ul>
}
</div>
);
}
}
Hope it will be help

This code will handle the object that is returned by api and also moves the fetching to componentDidMount.
constructor(props) {
super(props);
this.state = {
data: [],
}
}
componentDidMount() {
firebaseCon.content.get('text', { fields: ['id', 'title'] })
.then((response) => {
let data = [];
for (item in response) {
data.push(response[item]);
}
this.setState({ data });
});
}
render() {
let itemList = this.state.data.map(function(item) {
return <li className="item" key={item.id}>{item.title}</li>;
});
return (
<ul>
{itemList}
</ul>
)
}
A closer look at Promises' methods then and catch should make it clearer: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/then

Have you seen this?
https://github.com/axios/axios
Promise based HTTP client for the browser and node.js.
You can see it in action in this tutorial, used with React:
https://codeburst.io/building-appointment-scheduler-app-in-react-and-nodejs-f163c4eaab6b
Hope it helps.

Related

How to fetch Multiple news data using RSS Feed in React JS

I already done RSS Feed implementation. Now I'm able to fetch 1 News using this code. I want to fetch multiple news using this code. How can I do that.
Thank in advances.
class FetchDataFromRSSFeed extends Component {
constructor() {
super();
this.state = {
recentBlogPost: {
name: '',
url: ''
}
}
}
FetchDataFromRssFeed() {
var request = new XMLHttpRequest();
request.onreadystatechange = () => {
if (request.readyState == 4 && request.status == 200) {
var myObj = JSON.parse(request.responseText);
for (var i = 0; i < 1; i ++) {
this.setState({
recentBlogPost: {
name: myObj.items[i].title,
url: myObj.items[i].link
}
});
}
}
}
request.open("GET", "https://api.rss2json.com/v1/api.json?rss_url=https%3A%2F%2Fblog.codecarrot.net%2Ffeed.xml&order_dir=desc&count=30", true);
request.send();
}
componentDidMount() {
{this.FetchDataFromRssFeed()}
}
render() {
return (
<div>
Check out our blog: <a target="_blank" href={this.state.recentBlogPost.url}>{this.state.recentBlogPost.name}</a>
</div>
);
}
}
I already done RSS Feed implementation. Now i'm able to fetch 1 News using this code. I want to fetch multiple news using this code.
Instead of defining object like this,
recentBlogPost: {
name: '',
url: ''
}
you need to define an array,
recentBlogPost: []
Now instead of setting first record only,
var myObj = JSON.parse(request.responseText);
for (var i = 0; i < 1; i ++) {
this.setState({
recentBlogPost: {
name: myObj.items[i].title,
url: myObj.items[i].link
}
});
}
You need to set complete array,
var myObj = JSON.parse(request.responseText);
this.setState({
recentBlogPost: myObj.item
});
And finally displaying only 1 record as,
<div>
Check out our blog: <a target="_blank" href={this.state.recentBlogPost.url}>{this.state.recentBlogPost.name}</a>
</div>
You need to iterate over the state array,
{this.state.recentBlogPost.map(post=>{
return <div key={post.title}>Check out our blog: <a target="_blank" href={post.link}>{post.title}</a></div>
})}
Adding to the great explanation by #ravibagul91, here's the complete React component. Details on the strategy can be found in:
Array.prototype.map()
How to Use Async/Await in React
Lists and Keys
import React, { Component } from "react";
export default class FetchDataFromRSSFeed extends Component {
constructor() {
super();
this.state = {
items: []
};
}
async componentDidMount() {
try {
const response = await fetch(
"https://api.rss2json.com/v1/api.json?rss_url=https%3A%2F%2Fblog.codecarrot.net%2Ffeed.xml&order_dir=desc"
);
const json = await response.json();
this.setState({ items: json.items });
} catch (error) {
console.log(error);
}
}
render() {
console.log(this.state);
return (
<div>
Check out our blog:
{this.state.items.map(item => (
<li key={item.guid}>
<a target="_blank" rel="noopener noreferrer" href={item.link}>
{item.title}
</a>
</li>
))}
</div>
);
}
}
The unstyled results look like this:

Why I can not receive results of axios in ReactJs Datatables in render section

Good Day, I can receive the results of my simple object state variable "var blogs" in My datatables plugin in render section of my component, But if I try to change the variable in datatable in render section to variable that I get from axios "var blogs_ajax" I can not see the results of datatable action on my screen
That is my code, Please help me Sorry I can not put this code in action snippet because of including Datatables library
export default class AllBlogs extends Component {
constructor() {
super();
this.state = {
blogs: [
{
"title":"функция Query — подробное руководство",
"author":1,
"viewers":213
},
{
"title":"функция Query — подробное руководство",
"author":1,
"viewers":213
}
],
blogs_ajax:[]
};
this.navItems();
}
componentDidMount() {
}
async navItems() {
await axios
.post("http://localhost:8000/api/blogs/getAllBlogs/")
.then(response => {
//console.log(response);
return response;
}
)
.then(json => {
console.log(json.data.data)
if (json.data.success) {
this.setState(() => ({blogs_ajax: json.data.data.aaData}))
} else alert("Blogs Failed!");
})
.catch(error => {
alert(`An Error Occured! ${error}`);
});
}
render() {
const {blogs_ajax} = this.state;
console.log(this.state.blogs_ajax) // But I can see the result here
return (
<div id="content" className="animated fadeInUp">
<WidgetGrid>
<div className="row">
<article className="col-sm-12">
<JarvisWidget id="wid-id-0" editbutton={false} color="darken">
<header>
<span className="widget-icon">
<i className="fa fa-table"/>
</span>
<h2></h2>
</header>
<div>
<div className="widget-body no-padding">
<Datatable
options={{
data: this.state.blogs_ajax ,
// But I can not receive array of objects here from axios
columns: [
{data: "title"},
{data: "author"},
{data: "viewers"},
]
}}
Your render section is going first and you didnt get your axios result.Try to go this way check if results already presents
${this.state.postBlog.status ?
<Datatable
options={{
columns: [
{data: "title"},
{data: "author"},
{data: "viewers"},
]
}}
paginationLength={true}
className="table table-striped table-bordered table-hover"
width="100%"
>
Your state contains two properties, blogs and blogs_ajax.
In your render method, you create a const from the state, but this contains two objects, not the array of objects.
To me it looks like you have a small hard-coded list whilst getting your table working, but now it is you want to display the array in this.state.blogs_ajax.
Change the constant to this.state.blogs_ajax, or even better delete the constant and in the data tables Options object, just assign this.state.blogs_ajax. For clarity, datatables is expecting an array of objects, not an object.
OK, this issue was not how you're passing in the data now. It is more to do with how you're handling the data updates in your datatables.net wrapper.
I've used datatables quite a bit in Javascript and within React, but I've never seen the table instatiated in that way.
When the data does finally load, you need to check when or not to update the component. Saying true in shouldComponentUpdate usually does the trick, but not in the way you create the table.
I've two examples below.
Play.js
import Datatable from '../components/datatable';
import React from "react";
export default class AllBlogs extends React.Component {
constructor() {
super();
this.state = {
blogs: [
{
"title":"функция Query — подробное руководство",
"author":1,
"viewers":213
},
{
"title":"функция Query — подробное руководство",
"author":1,
"viewers":213
}
],
blogs_ajax:[]
};
}
componentDidMount() {
this.navItems();
}
async navItems() {
//CHA - simulating a small gap in between the table initially loading and the data arriving.
this.setState({blogs_ajax: this.state.blogs_ajax});
}
render() {
console.log(this.state.blogs) // But I can see the result here
return (
<div id="content">
<h1>React Table</h1>
<Datatable
options={{ data: this.state.blogs,
columns: [
{data: "title"},
{data: "author"},
{data: "viewers"},
]
}} />
</div>
);
}
}
Next is trimmed down example of your datatables wrapper from your repo. look at the comments prefixed CHA.
import React from "react";
import $ from "jquery";
require("datatables.net-bs");
require("datatables.net-buttons-bs");
require("datatables.net-buttons/js/buttons.colVis.js");
require("datatables.net-buttons/js/buttons.flash.js");
require("datatables.net-buttons/js/buttons.html5.js");
require("datatables.net-buttons/js/buttons.print.js");
require("datatables.net-colreorder-bs");
require("datatables.net-responsive-bs");
require("datatables.net-select-bs");
export default class Datatable extends React.Component {
componentDidMount() {
this.datatable(this.props.data);
console.log("CHA - In datables.js did mount - data will be undefined initially");
console.log(this.props.data);
}
shouldComponentUpdate(nextProps, nextState){
console.log("CHA - shouldcomponentupdate - we should now have data.");
console.log(nextProps.options.data);
console.log("CHA - because of the way you build the datatable, we need to rebuild instead of redraw.");
this.datatable(nextProps.options.data);
return true;
}
datatable() {
const element = $(this.refs.table);
let { options } = { ...this.props } || {};
let toolbar = "";
if (options.buttons) toolbar += "B";
if (this.props.paginationLength) toolbar += "l";
if (this.props.columnsHide) toolbar += "C";
if (typeof options.ajax === "string") {
let url = options.ajax;
options.ajax = {
url: url,
complete: function(xhr) {
// AjaxActions.contentLoaded(xhr)
}
};
}
options = {
...options,
...{
dom:
"<'dt-toolbar'<'col-xs-12 col-sm-6'f><'col-sm-6 col-xs-12 hidden-xs text-right'" +
toolbar +
">r>" +
"t" +
"<'dt-toolbar-footer'<'col-sm-6 col-xs-12 hidden-xs'i><'col-xs-12 col-sm-6'p>>",
oLanguage: {
sSearch:
"<span class='input-group-addon input-sm'><i class='glyphicon glyphicon-search'></i></span> ",
sLengthMenu: "_MENU_"
},
autoWidth: false,
retrieve: true,
responsive: true
}
};
console.log("before const call");
console.log(options);
const _dataTable = element.DataTable(options);
if (this.props.filter) {
// Apply the filter
element.on("keyup change", "thead th input[type=text]", function() {
_dataTable
.column(
$(this)
.parent()
.index() + ":visible"
)
.search(this.value)
.draw();
});
}
if (!toolbar) {
element
.parent()
.find(".dt-toolbar")
.append(
'<div class="text-right"><img src="assets/img/logo.png" alt="SmartAdmin" style="width: 111px; margin-top: 3px; margin-right: 10px;"></div>'
);
}
if (this.props.detailsFormat) {
const format = this.props.detailsFormat;
element.on("click", "td.details-control", function() {
const tr = $(this).closest("tr");
const row = _dataTable.row(tr);
if (row.child.isShown()) {
row.child.hide();
tr.removeClass("shown");
} else {
row.child(format(row.data())).show();
tr.addClass("shown");
}
});
}
}
render() {
let {
children,
options,
detailsFormat,
paginationLength,
...props
} = this.props;
console.log("CHA - In render");
console.log(this.props);
return (
<table {...props} ref="table">
{children}
</table>
);
}
}

How to make React.js fetch data from api as state and pass this state data from its parent to child component

I am working on a React.js + D3.js project. I wanted App.js to fetch data from a json file and save this data into state and pass this parent sate data down to my child component state through the property. I found if I use static data in App.js works fine, but once fetching from a json file, it failed because no data can be stored into property. My App.js like this:
import React, { Component } from 'react';
import SandkeyGraph from './particle/SandkeyGraph';
class App extends Component {
state = {
data : null
}
// works fine in this way!
// state = {
// data: {
// "nodes":[
// {"node":0,"name":"node0"},
// {"node":1,"name":"node1"},
// {"node":2,"name":"node2"},
// {"node":3,"name":"node3"},
// {"node":4,"name":"node4"}
// ],
// "links":[
// {"source":0,"target":2,"value":2},
// {"source":1,"target":2,"value":2},
// {"source":1,"target":3,"value":2},
// {"source":0,"target":4,"value":2},
// {"source":2,"target":3,"value":2},
// {"source":2,"target":4,"value":2},
// {"source":3,"target":4,"value":4}
// ]}
// }
componentWillMount() {
this.getData('./data/sankey.json');
}
getData = (uri) => {
fetch(uri)
.then((response) => {
return response.json();
})
.then((data) => {
// successful got the data
console.log(data);
this.setState({ data });
});
}
render() {
// failed
const { data } = this.state;
return (
<div>
<SandkeyGraph
height={300}
width={700}
id="d3-sankey"
sankeyData = {this.state.data}
/>
</div>
);
}
}
export default App;
parrt of my is like this:
class SankeyGraph extends Component {
displayName: 'SankeyGraph';
state = {
sankeyData : null
}
constructor(props) {
super(props);
this.state.sankeyData = props.sankeyData || null;
}
PropTypes : {
id : PropTypes.string,
height: PropTypes.number,
width: PropTypes.number,
sankeyData : PropTypes.object,
}
componentDidMount () {
// will be null, if using fetch from App.js
//console.log(this.state.sankeyData);
this.setContext();
}
//...
Does anyone know how to handle this situation? Thank you so much in advanced!
After working out the problem, it turned out that there was no problem with fetch. It just didn't account for null in any of the components in the program (It would crash after using a null value.
For example in render:
render() {
if (this.state.data) {
return (
<div>
<SandkeyGraph
height={300}
width={700}
id="d3-sankey"
sankeyData = {this.state.data}
/>
</div>
);
}
else {
return <div/>
}
}
Or, the use of a ternary operator would work as well to be more concise (answer by #Eliran):
return (
{this.state.data ?
<div>
<SandkeyGraph
height={300}
width={700}
id="d3-sankey"
sankeyData = {this.state.data}
/>
</div> : <div>No Data Available</div>
);
You can add in your render function a condition:
render() {
// failed
const { data } = this.state;
return (
<div>
{data ?
<SandkeyGraph
height={300}
width={700}
id="d3-sankey"
sankeyData={data}
/> : "Loading..."
}
</div>
);
}
and only if data is populated the component will be rendered.
...
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
data : null
}
}
It seems like an error on the state declaration?
1.- Import your json in App component on top like this: import jsonData from './data/sankey.json'
2.- Set jsonData in state jsonData in App component.
constructor(props) {
super(props)
this.state = {
jsonData : {}
}
}
componentWillMount() {
this.setState({ jsonData })
}
You do not need to fetch as you have your json locally.
Once you have your json in your state, you can display it in render like this for example:
this.state.jsonData.data.links.map(x=>
<div>
<div>x.links.source</div>
<div>x.links.target</div>
</div>
)
I've been testing and you need to replace the getData() method to this:
getData = (uri) => {
fetch(uri, {
headers : {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
})
.then((response) => {
return response.json();
})
.then((data) => {
// successful got the data
console.log(data);
this.setState({ data });
});
}
This is because you need to declare 'Content-Type' and 'Accept' headers on your fetch options in order to make your request.

react getting observable values in component

Seting the obsrv array in the component below;
class AnnouncementState {
#observable categories =[];
constructor(){
this.getAnnouncementCategory();
}
getAnnouncementCategory() {
fetch(`..`)
.then((response) => {
return response.json();
})
.then((response) => {
this.categories = response.value.map((item , i)=>{ return {Id:item.Id, Title:item.Title} });
}, (error) => {
});
}
}
I checked the retrieved values its ok. and I try set it in component and render it below;
#observer
class AnnouncementComponent extends React.Component {
categories = [];
componentWillMount(){
debugger
this.categories=this.props.announcement.categories;
}
render() {
const listItems = this.categories.map((item) => {
return (<li>...</li>)
});
return (
<div id="announcements-tab">
List Items:
<ul className="nav nav-tabs">
{listItems}
</ul>
</div>
);
}
}
I expected to see all list items but none(only "listItems" string)in html, no error in console. how can I fix and debug it ? using "debugger" keyword shows nothing for observable.
In your code, I don't see where are you creating the instance of AnnouncementState. Here an example how can you get the categories list.
e.g.
class AnnouncementState {
#observable categories =[];
#action getAnnouncementCategory() {
fetch(`..`)
.then((response) => {
return response.json();
})
.then((response) => {
this.categories = response.value.map((item , i)=>{ return {Id:item.Id, Title:item.Title} });
}, (error) => {
});
}
}
export default new AnnouncementState(); //here you can create the instance.
#observer
#inject('store') //here substitute with your store name, the name you set in your provider
class AnnouncementComponent extends React.Component {
componentWillMount(){
debugger
this.props.store.getAnnouncementCategory();
}
render() {
const listItems = this.props.store.categories.map((item) => {
return (<li>...</li>)
});
return (
<div id="announcements-tab">
List Items:
<ul className="nav nav-tabs">
{listItems}
</ul>
</div>
);
}
}
This should work, just be sure you pass the correct store with <Provider store={store}>.

React.Js 0.14 - Invariant Violation: Objects are not valid as a React child

I have 2 sub classes and 1 super class (3 components):
Navigation (super)
TopNavZone
MobileNavZone
export default class Navigation extends Component {
constructor(props) {
super(props);
this.state = {
navItems: [],
navMenu: []
};
}
fetchNavItems(clientId) {
NavMenuAPI.getAll(clientId)
.then((response) => {
const data = response.data;
this.setState({ navItems: data });
this.showNavMenu();
});
}
}
Each sub class calls the fetch method in componentDidMount, and then the fetch call, after getting the data, calls the respective sub class's showMenu method:
export default class TopNavZone extends Navigation {
constructor(props) {
super(props);
}
componentDidMount() {
const clientId = this.props.clientId;
// in super class
this.fetchNavItems(clientId);
}
showNavMenu() {
const navItems = this.state.navItems;
const results = [];
navItems.map((item, index) => {
results.push(
// whatever html markup belongs here for TopNavZone
);
});
this.setState({ navMenu: results });
}
render() {
if (!this.state.navMenu) return <div>Loading</div>;
return (
<div>{ this.state.navMenu }</div>
)
}
I know what the error message is telling me. I know React no longer allows objects to be rendered as a child. I tried ...
React.addons.createFragment(results)
in the showNavMenu and received the error that cannot create fragment of undefined.
I like as much of my html away from the render section and refactored into the respective functions that deal with it, so I really do not want to load up my render section with the showNavMenu markup. I'd just assume call it in one line from the render section.
What must I do to make this work and keep a tidy render section?
Many Thanks!
I found the solution and have kept the render section tidy.
The key to my solution lies in performing the mapping inside the render and NOT calling a function to perform that same said mapping.
So .......
Remove the this.showNavMenu() from the super class' fetchNavItems, and the navMenu array from state.
Render now looks like this:
render() {
if (!this.state.navItems) return <div>Loading ...</div>;
return (
<section>
<nav>
<ul>
{ this.state.navItems.map(this.showNavMenu.bind(this)) }
</ul>
</nav>
</section>
);
}
showNavMenu has changed to:
showNavMenu(item) {
const results = [];
let subMenu = [];
let childrenLength = 0;
if (item.children !== undefined) {
const children = item.children;
childrenLength = children.length;
subMenu = this.fetchSubMenu(children);
}
results.push(
<li key={ item.index }>
{ item.title }
</Link>
{ subMenu }
</li>
);
return results;
}
fetchSubMenu:
fetchSubMenu(children) {
const results = [];
children.map((child, idx) => {
results.push(
<div key={ idx }>
<Link to={ child.linkTo }>
{ child.title }
</Link>
</div>
);
});
return results;
}

Resources