React: Iterate over object, find array, and display array items - arrays

I'm trying to iterate over an object being return by the axios call to "/Home/NewsNotes".
Response from Axios
I'm able to display the property names to the screen, but I'm having issues accessing the "NewsNotes" array.
Here's my code for my component.
class ReleaseDetailsComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
releaseNotes: {}
};
}
componentDidMount() {
var _this = this;
const urlParams = new URLSearchParams(window.location.search);
const currCatId = urlParams.get('categoryId');
axios.get('/Home/NewsNotes?categoryId=' + currCatId)
.then(response => _this.setState(
{ releaseNotes: response.data }
))
.catch(function (error) {
console.log(error);
});
console.log(currCatId);
}
render() {
return (
<section>
<div className="row">
<div className="col-sm-8 col-sm-offset-2 col-md-10 col-md-offset-1">
<h2 className="page-title tutorials"><img className="title-icon" src="/Content/images/icons/icon-release-notes.png" /> News & Release Notes</h2>
<h3>{this.state.releaseNotes.Title}</h3>
{Object.keys(this.state.releaseNotes).map(function (item, key) {
return (
<p>{item}</p>
);
})}
</div>
</div>
</section>
);
}
}
ReactDOM.render(
<ReleaseDetailsComponent />,
document.getElementById('tutorialsWrapper')
);

I assume the object which you retrieved from URL using axios will look like:
var releaseNotes = {
NewsNotes: [
"Buy some milk": "data 1",
"Clean kitchen": "data 2",
"Watch Netflix": "data 3"
],
NewsNotesCategoryId: 3,
SortOrder: null,
Title: "1.0.1",
TypeId: 2
};
Next, you can inject "this" into map method as follow, check for any child array, then push each JSX block into the temp array in order to return later:
{Object.keys(this.state.releaseNotes).map((key, idx) => {
var temp = [];
temp.push(<p>{key}</p>);
if (Array.isArray(this.state.releaseNotes[key])) {
this.state.releaseNotes[key].map(ckey => {
temp.push(<p>{ckey}</p>);
});
}
return temp;
}, this)}

You can't render an array of elements using keys. Rather map over the array and print them like this.
renderReleaseNotes() {
return _.map(this.state.releaseNotes, releaseNote => {
return (
<p>releaseNote.title</p>
);
});
}
Hope this helps. Happy coding !

Related

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>
);
}
}

Display a limited number of items in the filtered array with a load more button

I have this component Cat that pulls the data from a local json file and displays all the Cats from the file in the alphabetical order. I need to display first 10 cats, and then have a Load More button to display the rest. Does anyone have a solution on how to do it in a good way? The line {providerNumber.length} Cats still needs to show the total number of cats, not the first 10.
Thank you!
import React, { Component } from 'react';
import { NavLink } from 'react-router-dom';
import Error from './Error.jsx';
export default class Cat extends React.Component{
constructor() {
super();
this.state = {
providersData: [],
loading: true
};
}
componentDidMount () {
setTimeout(() => this.setState({ loading: false }), 500);
fetch('../feed/sample.json')
.then(response => { console.log(response); return response.json()})
.then(responseData => {
console.log(responseData)
this.setState({ providersData: [...responseData.providers].sort((a,b) => {
const aName = a.companyName.toUpperCase()
const bName = b.companyName.toUpperCase()
if (aName < bName) {
return -1;
}
if (aName > bName) {
return 1
}
// names must be equal
return 0
})
});
})
.catch(error => {
console.log('Error fetching and parsing data', error);
});
}
render() {
const { loading } = this.state;
const providerNumber = this.state.providersData.filter(provider => provider.yearStarted >= 2010 && provider.type === 'cat')
if(loading) {
return (
<div> <img src="./../assets/loader.svg" alt=""/></div>
); // render loading when app is not ready
}
return this.state.providersData.length ? (
<div>
<h1>Cats</h1>
<div> {providerNumber.length} Cats</div>
{this.state.providersData.map(function(provider, index) {
if (provider.yearStarted >= 2010 && provider.type === 'cat') {
return (
<div key={index} className="job">
<h2>{provider.companyName}</h2>
<img src={provider.images['Company Logo'].url} alt=""/>
</div>
)
}
})}
</div>
) : <Error />
}
};
You can do something like this before you call .map in your render:
this.state.providersData.slice(0, this.state.numberOfCatsShown).map(...)
You would need to initialize this.state.numberOfCatsShown to 10 in your constructor first.
When you want to display more cats, use a function that looks like this:
showMoreCats() {
const newNumberOfCatsShown = this.state.numberOfCatsShown + 10;
// set it to the length of the array to show all the cats.
this.setState({ numberOfCatsShown: newNumberOfCatsShown });
}

Why declearing function(Element) outside render doesn't work?

I want to create a dynamic Element inside parent Component class. It gives Unexpected token at function declaration. However writing same function inside return(..here..) works. What am I missing?
This is my code:
import React, { Component } from 'react';
import '../App.css';
var axios = require('axios');
class DisplayRevenue extends Component {
constructor(props){
super(props);
this.state = { data:[] }
}
componentWillMount() {
this.loadRevenue(this.props.url, this.props.token);
}
setData(data){
this.setState(data:data);
console.log(this.state.data); //this gives output as json object
}
loadRevenue(url,token){
axios({
method:'get',
url:url,
headers: {
Authorization: `Bearer ${token}`,
},
})
.then( (response) => {
// console.log(response.data);
this.setData(response.data);
})
.catch(function (error) {
console.log("Error in loading Revenue "+error);
});
}
var ListData = this.state.data.map( (invoice) => {return <div>{invoice.customerNumber}</div>})
//above function gives error
render() {
//var listData = this.state.data.map( (invoice) => (<div>{invoice.customerNumber}</div>)
return (
<div>
<h3>MonthToDate</h3>
{this.state.data.map((invoice) => {return <div>{invoice.customerNumber}</div>})}
</div>
);
}
}
export default DisplayRevenue;
I have json object array as below:
"data": [
{
"customerId": 0,
"customerNumber": "IT8SDS",
"customerType": "RVN",
"invoiceType": "LBR",
"invoiceAmt": "52651.2287",
"invoiceStatus": "BILLED",
"invoiceDate": "2016-12-30T00:00:00.000Z"
},
{
"customerId": 1,
"customerNumber": "DC0WTY",
"customerType": "RVN",
"invoiceType": "RNT",
"invoiceAmt": "198503.1828",
"invoiceStatus": "BILLED",
"invoiceDate": "2016-12-30T00:00:00.000Z"
},
{
"customerId": 2,
"customerNumber": "LK8MD5",
"customerType": "INT",
"invoiceType": "EQT",
"invoiceAmt": "-6833.70721",
"invoiceStatus": "PENDING",
"invoiceDate": "2016-12-30T00:00:00.000Z"
},
{
"customerId": 3,
"customerNumber": "E03PTJ",
"customerType": "PCT",
"invoiceType": "PTS",
"invoiceAmt": "55670.17911",
"invoiceStatus": "BILLED",
"invoiceDate": "2016-12-19T00:00:00.000Z"
},
NOTE: Writing {this.state.data.map((invoice) => {return <div>{invoice.customerNumber}</div>})} inside return(..here..) in render() works.
You can't declare variables inside a class body.
You can do that inside functions (such as render, constructor, react life cycle methods, custom functions etc...).
If you want to do it the "react way" make ListData as a component:
Example:
const ListData = data => (
<div>
{data.map( (invoice) => <div>{invoice.customerNumber}</div>)}
</div>
);
And use it like so:
render() {
return (
<div>
<h3>MonthToDate</h3>
<ListData data={this.state.data} />
</div>
);
}
Here is a working example:
const ListData = ({data}) => (
<div>
{data.map((o) => (<div>{o}</div>))}
</div>
);
const App = () => (
<div>
<h2>Hello</h2>
<ListData data={["Hi", "i'm", "a", "test"]} />
</div>
);
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>

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}>.

Having trouble getting this function to bind correctly in react

My handleTeamChange function is erroring and coming back as undefined when the renderTeamMethod runs. I tried passing the variable team into on like "this.handleTeamChange.bind(this, team)" as well but nothing. I've tried a ton of different ways to call teh handleTeamChange method but so far nothing but undefined. Any thoughts?
import React, { Component } from 'react';
import UserDropdown from './user-dropdown';
import { getTeams } from 'api/index.js';
let teams = [];
let selectedTeamID = null;
let selectedTeamName = 'all_teams';
let teamId = '';
export default class TopNav extends Component {
constructor(props, context) {
super(props, context);
// this.handleTeamChange = this.handleTeamChange.bind(this);
this.state = {
teams: [],
team: {},
selectedTeamID: null,
selectedTeamName: 'All Teams',
teamSelection: false
};
}
handleClick() {
this.setState({
teamSelection: true
});
}
componentWillMount() {
getTeams().then((response) => {
teams = response.data;
this.setState({teams: teams});
});
}
renderTeams() {
return teams.map(function(team) {
if (team.active === true) {
return (
<div
onClick={ () => { this.handleTeamChange(team) } }
className="team-filter-team"
key={team.id}
value={team.id} >{team.TeamName}
</div>
);
}
});
}
handleTeamChange(team) {
console.log(team);
}
render () {
return (
<nav className="nav-wrapper">
<img className="logo-medium nav-logo" src={"https://s3-us-west-2.amazonaws.com/mvtrak/MVTRAKbrandmark.png"} />
<div onClick={ this.handleClick.bind(this) } className="team-selected"> { this.state.selectedTeamName } </div>
<div className="team-filter-container">
{this.renderTeams()}
</div>
<UserDropdown />
</nav>
);
}
}
the function body where you're mapping teams is not bound to the component's scope, therefore this is undefined.
change teams.map(function (team) { ... }) to e.g. a fat arrow teams.map((team) => ... ):
return teams.filter(team => team.active).map((team) => (
<div
onClick={ () => { this.handleTeamChange(team) } }
className="team-filter-team"
key={team.id}
value={team.id}
>
{team.TeamName}
</div>
))

Resources