loop data from state with react.js - reactjs

I'm doing my first reactjs app and i have run into some troubles.
This is my feature (child) component that i call from my base file.
var ReactDOM = require('react-dom');
var React = require('react');
var ConfigurationService = require('../configurationService');
class Feature extends React.Component {
constructor(props) {
super(props);
this.state = {
features: null
};
this.getConfiguration();
}
getConfiguration() {
var self = this;
var config = ConfigurationService.getConfiguration('test', 'test').then(function (config) {
self.setState({ features: config.data.Features })
});
}
render() {
if (this.state.features) {
return (<div> {
this.state.features.map(function (feature) {
<span>feature.Description</span>
})
}
</div>)
}
else {
return <div>no data</div>
}
}
}
module.exports = Feature;
It calls my api and collects data that looks like this:
For like a 10th of a second it shows the "no data" but then i guess that it succeeds to grab the data and that this.state.features no longer is null.
But even though features isn't null it doesn't show anything in the browser.

Because you are not returning anything inside map body, 2nd you need to use {} to print feature.Description because its a dynamic data, and 3rd is you need to assign unique key to each element inside loop otherwise it will throw warning.
Use this:
if (this.state.features) {
return (
<div>
{
this.state.features.map(function (feature, i) {
return <span key={feature.Id}>{feature.Description}</span>
})
}
</div>
)
....
Or you can use arrow function also, like this:
if (this.state.features) {
return (
<div>
{
this.state.features.map((feature) => <span key={feature.Id}> {feature.Description} </span>)
}
</div>
)
....

That's not how map works. You need to have a return statement inside the map which is basically for each element of the array return so and so.
return (
<div> {
this.state.features.map(function (feature) {
return (<span key={feature.Id}>{feature.Description}</span>)
})
}
</div>
)
Here for example for each feature it is returning a span with the contents as feature.Description.
Also like Mayank Shukla pointed out a key is important. keys are basically used by react to compare the new DOM when state changes to the old DOM and make only those changes which are required (instead of re-rendering the entire component). The keys have to be unique so use the feature.Id as a key as that is unique. Don't use array indices as keys as the array might change and then the indices will point to wrong elements in the new array.

Related

Cant use filter function in the return statement for reactjs. Throwing error

I'm trying to filter the data directly under the return statement. I am getting this error "Objects are not valid as a React child. If you meant to render a collection of children, use an array instead". Map function works just fine. Map and Filter both return array
Here's my code
export class TestPage extends Component {
constructor(){
super();
this.state = {
proPlayerData: []
}
}
componentDidMount(){
this.fetchData();
this.filterData();
}
filterData = () => {
}
fetchData = async() => {
const playerData = await fetch("https://api.opendota.com/api/playersByRank");
const player_data = await playerData.json()
console.log("fetch",player_data);
await this.setState({proPlayerData: [...player_data]})
}
render() {
// let topTenIds = this.state.proPlayerData
// console.log(topTenIds)
return (
<div>
{this.state.proPlayerData.filter((data,index) => {
if(index <= 10){
return <div key={index}>data.accountId</div>
}
})}
</div>
)
}
}
export default TestPage
Why can't I use filter just like map?
Array.prototype.map transforms data from one format to another, its used in react a lot to transform your arrays of data into JSX
Array.prototype.filter will filter data in your data arrays, but not alter the format, therefore if you start with an array of objects, you will end with an array of objects of the same shape (or an empty array if none meet the condition in the callback)
You need a combination of both, first a filter to filter the data you want, then a map to transform your filtered data into JSX, but even still rather than a filter, which will iterate over each element, you only need the first 10, looking at your example, therefore you can use Array.prototype.slice -
this.state.proPlayerData
.slice(0, 10)
.map((data) => (<div key={index}>{data.accountId}</div>))
edit... looks like you maybe want to the first 11, therefore update the slice args to suit...

react render variable undefined

for some reason that i dont understand, i cant seem to fetch a state value in my renderer, at first i thought it was a scoping issue, but even after changing to var, my variable is undefined.
constructor(props) {
super(props);
this.state = {
stuff: {}
};
componentDidMount(){
this.getatuff.then( (result) =>{
this.setState({
stuff: result.items[0]
});
});
console.log('REACT Coveo component DOM loaded');
}
render() {
var ReactMarkdown = require('react-markdown');
var project = this.state.stuff;
debugger;
if(!Object.entries(project).length === 0){
var asd = project.body[1].value; <---UNDEFINED
return (
<div className="block">
<ReactMarkdown source={asd} />
</div>
);
}
why is my Object array value undefined in the renderer?
Note: both const variables in the screenshot were changed to var, and the behavior persists.
You'd need to define state inside your class, or inside your constructor():
constructor(props) {
super(props);
this.state = {
project: // something
}
}
componentDidMount() {
// etc.
It's not clear on your example what you're trying to achieve, but it could be one of these reasons:
on your componentDidUpdate you have a typo in this.getatuff (I assume you're trying to say getStuff
project seem to be defined on your renderer, your screenshot shows that it has a key id:5 and some others, but it might not have a key body or body might not be an array, or the array might contain only 1 value [0] instead of two values [0] and [1]. I suggest you to debug the structure of project to get the right value
You got syntax error here: if(!Object.entries(project).length === 0) it should be if (!(Object.entries(project).length === 0))
render() {
var ReactMarkdown = require('react-markdown');
var project = this.state.stuff;
debugger;
if (!(Object.entries(project).length === 0)) {
var asd = project.body[1].value; <---UNDEFINED
return (
<div className="block">
<ReactMarkdown source={asd} />
</div>
);
}

What is the correct way to return map iterator from nested component?

I constantly end up generating various errors when trying to return (more complex) values from my render method. Most of which, I was able to resolve. But this time, the component doesn't render at all, yet... there is no console error.
I understand the standard return(...) syntax, when it comes to returning single <div/> elements, but in some cases I don't fully understand why it should break my code.
Code examples I'm experimenting with:
class RenderMe1 extends React.Component {
constructor(props) {
super(props);
this.state = { list: ['A','B','C'] }
}
render() {
return(
<div>
<div>
/* === Works === */
{ this.state.list.map((object, index) => this.state.list[index] ) }
</div>
</div>);
}
}
class RenderMe2 extends React.Component {
constructor(props) {
super(props);
this.state = { list: ['0','0','0'] }
}
render() {
return(
<div>
<div>
/* === Doesn't Work === */
{ this.state.list.map((object, index) => { this.state.list[index] } ) }
</div>
</div>);
}
}
ReactDOM.render(<RenderMe1 />, document.getElementById("root")); // works
ReactDOM.render(<RenderMe2 />, document.getElementById("root2")); // doesn't work
For practice purposes, I'm using in-browser babel plugin, hence the JSX syntax.
The code inside brackets still works simply because while JavaScript calculates the statement, but it doesn't return it.
In this particular case, the issue is with the arrow function itself, though. There is a specific rule arrows follow: when {} brackets are skipped, the statement is in fact treated as a return value, without having to use return keyword. This cleans the code a bit. But soon as you add the {} brackets, whatever is inside must be explicitly returned using the return keyword. The following will fix your issue:
{ this.state.list.map((object, index) => { return(this.state.list[index]) } ) }
Remember, React is still really just JavaScript (often ES6+.) Hope this helps.
Your first example is an implicit return. meaning, you don't need to specify the return keyword because you haven't used curly braces and as such it returns the expression
The second one you have so it isnt returning anything. Here's the amended code
class RenderMe2 extends React.Component {
constructor(props) {
super(props);
this.state = { list: ['0','0','0'] }
}
render() {
return(
<div>
<div>
{ this.state.list.map((object, index) => { return this.state.list[index] } ) }
</div>
</div>);
}
}
I assume you're doing it this way because its an example but you could just do
this.state.list.map(listItem => listItem);
The reason the second approach doesn't work is a syntactice subtlty with arrow functions.
When you wrap an arrow function that contains a single statement with { and }, then that arrow function is effecitvly equivilant to returning undefined.
So for instance:
(object, index) => { this.state.list[index] }
is equvilant to:
function(object, index) {
this.state.list[index];
// Notice that nothing is returned
}
Conversly, by excluding the { and }, as you are doing in the working version:
{ this.state.list.map((object, index) => this.state.list[index] ) }
that is effectivly equaivlant to doing:
{ this.state.list.map(function(object, index) {
// The function is returning something
return this.state.list[index]
}
This return behaviour that "comes for free" with the non { .. } arrow functions, is the reason the first version works, and the latter version fails. For more information, see this MDN article

How to render properties of an Array of objects in React?

So, I have the exact same problem as our friend here :
How to render properties of objects in React?
The below (upvoted) solution by Nahush Farkande :
render() {
let user = this.props.user || {};
....
{user.email}
....
}
works for me... if user is an object. However, in my specific case, the data I fetch and want to render is an array of objects.
So, I return something like that :
<ul>
{
user.map( (el, idx) => {
return (
<li key = {idx}>
<div className="panel-body clearfix">
{el.title}
</div>
</li>
)
})
}
</ul>
That doesn't work. I get an error message that tells me that user.map is not a function (before [HMR] connected).
I expected that once the API fetches the user array of objects, the component would re-render and then the component would show the list of titles from each object of the user array (after [HMR] connected).
If your user (I recommend to rename to users) is an array, then you cannot use {} as the default. You should use [] as the default value:
const user = this.props.user || []
or, you can use a completely different branch to handle the loading case:
if (!this.props.user) {
return (
<div> ... my loading placeholder ... </div>
);
}
You already have correct answer but just wanted to give a running example.
Initialize your data in your state with the default values e.g
in case of object -> {}
in case or array -> []
Obviously in each case your rendering logic should be different e.g in case of array you need the map to loop over array and generate jsx element.
So when ever your component receives the updated data ( either it can be empty data or complete data) either via api call or via prop changes use the appropriate life cycle method such as componentWillReceiveProps or componentDidMount to get the latest data and again set the state with latest data.
For example when data is received via api call ->
constructor() {
this.state = {
data : []
}
}
componentDidMount()
{
this.getFunction();
}
getFunction = () => {
this.ApiCall()
.then(
function(data){
console.log(data);
// set the state here
this.setState({data:data});
},
function(error){
console.log(error);
}
);
}
So at the time of initial render your data will be either empty object or empty array and you will call appropriate rendering method for that accordingly.
class Test extends React.Component {
constructor(props) {
super(props);
this.state = {
data : []
}
}
componentWillMount() {
this.setState({ data : this.props.dp });
}
renderFromProps() {
return this.state.data
.map((dpElem) =>
<h3>{dpElem.name}</h3>
);
}
render() {
return (
<div>
<h1> Rendering Array data </h1>
<hr/>
{ this.renderFromProps()}
</div>
);
}
}
const dynamicProps = [{ name:"Test1", type:"String", value:"Hi1" },
{ name:"Test2", type:"String", value:"Hi2" },
{ name:"Test3", type:"String", value:"Hi3" }
];
ReactDOM.render(
<Test dp={dynamicProps} />,
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>

ReactJS - Showing some elements and a button to show more

I'm using React, with the Flux architecture.
In a certain part of my app, I need to show some elements (let's say, 6) and, if there's more, a button that will display the rest of them when clicked.
As I'm using Flux, I want my components to be as stateless as possible. So far I've tried:
let elements = [...] //Array with elements, passed as a prop to MyApp
class MyApp extends React.Component {
...
render(){
var elements = this.props.elements.map((elem, i) => {
if (i == 5)
return <More />
if (i > 5)
return;
return (
<ShowElement key={i} />
)
return <div>{elements}</div>
}
}
I think I could make this work by passing the remaining elements to my More component as prop, but it feels wrong. Is there a better way to do this?
As far as showing 6 items and rest when button is clicked, this is way to do it using state:
let elements = [...] //Array with elements, passed as a prop to MyApp
class MyApp extends React.Component {
getInitialState() {
return {
all: false
};
},
render(){
return <div>{this._renderElements(this.props.elements, this.state.all)}</div>
},
_renderElements(elements, showAll) {
const _elementsToRender = [];
const _elements = elements.splice(0); // clone array
let remainder = [];
if (!showAll) {
remainder = _elements.splice(6);
}
_elements.forEach(el => {
_elementsToRender.push(
<ShowElement {..el} />
);
});
if (remainder.length > 0) {
_elementsToRender.push(
<button onClick={evt => this.setState({ all: true })}>show all</button>
);
}
return (
<div>
{_elementsToRender}
</div>
);
}
}
And if you want to keep your component stateless you could pass something like showAll prop and make <button> (or whatever custom element you use) trigger action that will change showAll value in store and emit new value so component updates that way.
Wrap your more elements in a span or div with a style={moreSty}. The default for moreSty is display: 'none'. The click method for the more button will set the moreSty to display: 'inline' or 'block'.

Resources