JSX skip if JSON field does not exist - javascript

I am currently working with a JSON file from an API that does not add a key pair if the field is null. This is causing me grief when trying to iterate through the entire JSON file.
My code currently is
var ListOfItems = React.createClass({
render: function () {
var itemList = jsonFile.data.map(function(item)
{
return <Item key={item.__key}
itemQuestion={item.question}
itemQuestionAnswer={item.answer.answer}
userName={item.user.name}
staffName={item.staff.name}
staffImage={item.staff.image_url} />
});
return (
<div>
<ul>{itemList}</ul>
</div>
);
}
});
Which gives an error for when item.answer.answer has no value.
Any help will be greatly appreciated.

You can do that by adding a condition to check, whether the item's answer type is not undefined. If it's not, proceed with returning a value, else don't return anything. This way, you only append another item if the condition has passed (I used shorthand for the condition).
var ListOfItems = React.createClass({
render: function() {
var itemList = jsonFile.data.map(function(item)
{
typoeof item.answer != 'undefined' &&
return <Item key={item.__key} itemQuestion={item.question}
itemQuestionAnswer={item.answer.answer} userName={item.user.name}
staffName={item.staff.name} staffImage={item.staff.image_url} />
});
return (
<div>
<ul>{itemList}</ul>
</div>
);
}
});
If you always get item.answer but it's answer is either undefined or null, you can check for item.answer.answer in the code I've provided instead.

Depending on how big your list of items is, you could use the builtin filter Array method to first remove all the items you don't want, and then proceed to map through them. Keep in mind this will potentially go through your entire list twice.
A note about returning undefined from inside map. This will not prevent an item from getting returned. You will instead have an undefined item in your resulting array. The array will not be shorter.
Here's an example with filter():
var ListOfItems = React.createClass({
renderItems: function() {
return jsonFile.data
.filter(function(item) {
// This will return any item that has a truthy answer
return item.answer && item.answer.answer;
})
.map(function(item) {
return (
<Item
key={item.__key}
itemQuestion={item.question}
itemQuestionAnswer={item.answer.answer}
userName={item.user.name}
staffName={item.staff.name}
staffImage={item.staff.image_url} />
);
});
},
render: function() {
return (
<div>
<ul>
{this.renderItems()}
</ul>
</div>
);
}
});

Related

Function passed to element ref in React never runs

I'm building an array of React symbols, and passing a function to ref which never runs, for some reason. I believe I'm following the documentation on how to do this. By the time this is done rendering this.divElement should be set to the native DOM node of the last element, but since the function never runs, it remains null.
divElement = null;
render() {
const activeKeys = ["test1", "test2", "test3"];
const infoDivs = activeKeys.map((key, i) => {
console.log(key, i);
return (
<div
className="test"
ref={ (divElement) => { alert('This never runs?'); this.divElement = divElement } }
>
Some div text.
</divs>
)
});
return (
<span className="info-container container">
{ this.infoDivs }
</span>
)
}
I know the loop is running, as it logs three times as expected. What am I missing?
Apparently you meant to use {infoDivs}, not { this.infoDivs }. Your infoDivs variable that returns your elements, is just a variable inside render, not a class field.
Elements from infoDivs are never rendered, so the divElement remains undefined.
return (
<span className="info-container container">
{infoDivs}
</span>
)

How to use a constant in map return function ReactJS

I am wondering how can I use a constant in the map function, basically meaning: I have saved correctly the option I want from my falling menu regarding the constant (I checked it with console.log), for instance I have a name chosen and then I want to use it in the map function but unfortunately I get all the elements undefined when I use the constant; when I replace the constant with a directly written "name", I get all the elements correctly with their names.
Filterhosts=() =>{
var newState = this.state.array.slice(); // in the state array is empty
const selectedOption = this.state.selectedOption;
const writtenOption = this.state.writtenOption;
console.log(selectedOption) //ok
const namearray= this.state.filteredhosts.map(host=> {
return (
host.software.map((sub, subindex) => {
if(selectedOption=="name" || selectedOption=="vendor") {
newState.push(sub.selectedOption) //when I write sub.selectedOption , I receive empty array with all elements as undefined otherwise I become the names of all elements
}
else {
if(sub.vulnerable==true){
newState.push(sub.vulnerability.cve)}
}
})
)
})
const filteredarray = newState.filter( function(item){
return item === writtenOption // here I become properly the searched name//vendor
}
// how to show the whole info for the searched name/vendor(cpe, cve, cvss etc.)
)
console.log(newState); //ok
console.log(filteredarray); //ok
}
Oh I see.
sub.name
is the same as
sub["name"]
which is also the same as
sub[selectedOption]
IF selectedOption is "name". So just use newState.push(sub[selectedOption]) and I think that should work for you.

Component not rendering within .map forEach

I'm having trouble getting a component to render within .map
render: function() {
var self = this;
var ListItems = this.props.data.map(function(data){
self.props.fields.forEach(function(field, i) {
if (field.fieldKey in data) {
console.info(field.fieldKey + ': ' + data[field.fieldKey]);
return (<ListItem {...data} key={'field-' + i}/>)
} else {
console.error(field.fieldKey + " doesn't exist. Please make sure you match your 'fieldKey' to an existing column in your data table");
}
});
});
return <tr onDoubleClick={this.handleEditRow} onClick={this.handleSelectRow}>
{ListItems}
<td className="text-center">
<span className="btn"><input type="checkbox" ref="deleteCheckbox" checked={this.props.checked} onChange={this.handleDeleteChange}/></span>
<a className="btn" onClick={this.handleDeleteRow} title="Delete this Item"><i className="md md-close"></i></a>
</td>
</tr>
},
So my ListItem doesn't show at all. If I move it under the first loop, it shows fine. Can anyone tell me what I'm doing wrong?
UPDATES
JSON DATA
http://pastebin.com/MLbR77tG
So, I'm creating a list view. Basically, each item under data has a fields setting attached via the fieldKey
So the plan is to spit out the data, but use the configuration options under fields to format the list view. i.e. fieldKey is a dropdown, it's dataSource is message yada yada.
Name(data: test 1) (fields: is dropdown) | calling_gt(data: 123456) (fields: is text) | op_code (data: 5678) (fields: is dropdown)
Check out Array.prototype.forEach() - MDN
forEach() executes the callback function once for each array element; unlike map() or reduce() it always returns the value undefined and is not chainable. The typical use case is to execute side effects at the end of a chain.
For example, check out this jsfiddle. When you return a value within forEach() method, the value will not be received by the outer function, in this case, map loop. Instead, the outer loop will always get a undefined from the forEach() loop.
Your return statement is inside the call to forEach meaning it can't affect the outer map statement.
This means that the map function is returning undefined each time, which is why the component doesn't render.
Instead, you can write a predicate function that you can use to filter out rows that aren't used.
hasValidFieldKeys: function(fields, data) {
var invalidFields = fields.filter(function(field) {
var hasKey = field.fieldKey in data;
if(!hasKey) {
// log error
}
return !hasKey;
});
return invalidFields.length > 0;
}
Then use the predicate function inside component's render method.
var ListItems = this.props.data
.filter(function(data) {
// remove the rows that aren't valid
return self.hasValidFieldKeys(self.props.fields, data);
})
.map(function(data, index) {
return data.map(function(data, index2) {
var compositeKey = 'field-' + index + ':' + index2;
return <ListItem {...data} key={compositeKey}/>;
});
});
If it's important that your app stops if there is data missing certain field keys, then throw errors, rather than using console.error.

how to access vairables outside of map function in js and jsx in a React component

var PieceList = React.createClass({
render: function() {
var pieces;
if (this.props.pieces && this.props.onDeletePiece2) {
var pieces = this.props.pieces.map(function (piece) {
return (
<Piece pieceData={piece} onDeletePiece3={this.props.onDeletePiece2} />
)
});
}
return (
<div className="piecesTable">
{pieces}
</div>
);
}
});
I'm stumped as to how to get this to work. The problem is that {this.props} is not available inside of the map function.
Would a foreach be better here? stumped, pls halp!
map is just a regular JavaScript method (see Array.prototype.map). It can take an argument that sets the context (.map(callback[, thisArg])):
var PieceList = React.createClass({
render: function() {
var pieces;
if (this.props.pieces && this.props.onDeletePiece2) {
var pieces = this.props.pieces.map(function (piece) {
return (
<Piece pieceData={piece} onDeletePiece3={this.props.onDeletePiece2} />
)
}, this); // need to add the context
}
return (
<div className="piecesTable">
{pieces}
</div>
);
}
});
I would suggest going back and reading about this in JavaScript. When you pass an anonymous function to most methods (like .map, .forEach, etc.), it takes the global context (which is almost always window). If you pass in this as the last argument, since that this is referring to the class you just created with React.createClass, it'll set the correct context.
In other words, the way you were trying to do it was access window.props, which obviously doesn't exist. I'd if you opened your console to debug, you'd see the error Object Window doesn't have the property "props" or something very obfuscated.
EDIT 2: React 0.14.x
You can now define stateless functional components for components that do not require complex lifecycle event hooks or internal state
const PieceList = ({pieces, onDeletePiece2}) => {
if (!onDeletePiece2) return;
return (
<div className="piecesTable">
{pieces.map(x => (
<Pieces pieceData={x} onDeletePiece3={onDeletePiece2}>
))}
</div>
);
};
EDIT 1: ES6
As ES6 continues to become more prominent, you can also avoid nitpicky context issues by using an ES6 arrow function.
class PieceList extends React.Component {
renderPiece(piece) {
return <Piece pieceData={piece} onDeletePiece3={this.props.onDeletePiece2} />;
}
render() {
if (!this.props.onDeletePiece2) return;
return (
<div className="piecesTable">
{this.props.pieces.map(piece => this.renderPiece(piece))}
<div>
);
}
}
To get this to run in most environments, you'd need to "transpile" it using something like babel.js
The quick answer is that you need to bind the proper this to the map callback by passing this as the second arg
this.props.pieces.map(..., this);
This might be a better way to write your component tho
var PieceList = React.createClass({
renderPiece: function(piece) {
return <Piece pieceData={piece} onDeletePiece3={this.props.onDeletePiece2} />;
},
render: function() {
if (!this.props.onDeletePiece2) return;
return (
<div className="piecesTable">
{this.props.pieces.map(this.renderPiece, this)}
</div>
);
}
});
Regarding your comment about map
var x = {a: 1, b: 2};
['a', 'b'].map(function(key) {
// `this` is set to `x`
// `key` will be `'a'` for the first iteration
// `key` will be `'b'` for the second iteration
console.log(this[key]);
}, x); // notice we're passing `x` as the second argument to `map`
Will output
// "1"
// "2"
Notice how the second argument to map can set the context for the function. When you call this inside the function, it will be equal to the second variable that was sent to map.
This is JavaScript basics and you should definitely read up more here
Are you using a transpiler -- something like Babel? If so, this code will work fine:
if (this.props.pieces && this.props.onDeletePiece2) {
var pieces = this.props.pieces.map((piece, i) => {
return (
<Piece pieceData={piece} onDeletePiece3={this.props.onDeletePiece2} key={i}/>
)
});
...
If you can't use a transpiler, you could do this:
if (this.props.pieces && this.props.onDeletePiece2) {
var that = this;
var pieces = that.props.pieces.map( function(piece, i) {
return (
<Piece pieceData={piece} onDeletePiece3={that.props.onDeletePiece2} key={i}/>
)
})
...

React JS: Explanation of this.props.items.map feature

I am using React JS for Rendering the HTML content. The issue is I am not able to understand particular section of code what it does.
If you can see a basic sample of a Todo List from the below link
http://facebook.github.io/react/
<script type='text/jsx'>
/** #jsx React.DOM */
var TodoList = React.createClass({
render: function(){
var createItem = function(itemText) {
return <li>{itemText}</li>;
};
return <ul>{this.props.items.map(createItem)}</ul>;
}
});
var TodoApp = React.createClass({
getInitialState: function(){
return {items:[], text: ''}
},
onChange: function(e)
{
this.setState({text: e.target.value});
},
handleSubmit: function(e)
{
e.preventDefault();
var nextItems = this.state.items.concat([this.state.text]);
var nextText = ''
this.setState({items: nextItems, text: nextText});
},
render:function(){
return (
<div>
<h3>ToDo List</h3>
<TodoList items={this.state.items}/>
<form onSubmit={this.handleSubmit}>
<input type="text" onChange={this.onChange} value={this.state.text}/>
<button>Add #{this.state.items.length+1}</button>
</form>
</div>
)
}
});
React.render(<TodoApp />, document.getElementById('toDoListApp'));
</script>
I am basically not able to understand what map does and how create item parameters are working. Could anyone provide details on the same:
var TodoList = React.createClass({
render: function(){
var createItem = function(itemText) {
return <li>{itemText}</li>;
};
return <ul>{this.props.items.map(createItem)}</ul>;
}
});
Thanks,
Ankit
map is not a feature of React.js. You can call this function on any array you want. You should look at its documentation at MDN for that.
Basically, map is for converting an array to another array with modified items.
For example:
[1,2,3].map(function(item){
return item+1;
})
would return a new array like this: [2,3,4]
In your example, map is used to convert an array with items of type "string" to an array of React.DOM.li elements.
The autor of your example could also have done it like this
var TodoList = React.createClass({
render: function(){
return <ul>{this.createItems(this.props.items)}</ul>;
},
createItems: function(items){
var output = [];
for(var i = 0; i < items.length; i++) output.push(<li>{items[i]}</li>);
return output;
}
});
props is an object containing properties passed from a parent to a child component.
So props.items is the property named items which is an array.
props.item.map() maps the items arrary to an array of lis.
It will take this.props.items array, pass each item to the createItem function, and then return an array of the returned values of each call.
Specifically for that code, if you had this in this.props.items:
["Item 1 text", "Item 2 text", ..]
You'd get something like this from the map call:
["<li>Item 1 text</li>","<li>Item 2 text</li>",..]
this.props.items is an array and map return the new array according to callback function that provide as an first argument, it is quit easy to using ES6 and JSX.
<tr> { this.arr.map((obj, i) => <td key={i}> {obj.name} </td>) } </tr>
In our example it will return array of td's

Categories

Resources