I have 3 components:
Main
-- adItem
-- Offerslist
--- offers (just li's, not a comp)
//On the main comp containing state I add the offer and I pass the function down as a prop:
_addOffer: function(offerData) {
var timestamp = (new Date()).getTime();
// add offer to item
this.state.advitems[offerData.itemId].offers['offer-id' + timestamp] = offerData;
this.setState({
advitems: this.state.advitems
});
},
/*
AdItem
*/
var AdItem = React.createClass({
_addOffer: function(event) {
var timestamp = (new Date()).getTime();
var offerData = {
offerId: 'offer-' + timestamp,
itemId: this.props.index,
price: this.refs.offerprice.value
};
},
render : function() {
//some code to hide and show with css classes
{renderOffers}
)
}
});
var Offerslist = React.createClass({
_renderOffers: function(key) {
var details = this.props[key];
return (
<li className="offer-of-item">
€ {details.price}
<a className="remove-offer" onClick={this._removeOffer}>X</a>
</li>
)
},
render : function() {
return (
<ol className="list-of-offers-per-item">
{Object.keys(this.props).map(this._renderOffers)}
</ol>
)
}
});
AdItem can contain multiple offers. When I enter the first offer, I get the warning that it should contain a unique key. I checked and it DOES have a unique key. When adding the second, I don't get the warning. But I'm unsure whether it just stops warning me or it's corrected in some way.
Am I doing something wrong here? I'm unsure of what to check next.
When you iterate an array and create elements dynamically, you also have to add a key prop to said elements:
{Object.keys(this.props).map(this._renderOffers)}
this._renderOffers must return elements with keys that are unique relative to each other. The shortcut is to simply use the index in the array, but a much better approach that ensures that React knows how to properly tell one element from another is to use the offer item's id, if you know that it's unique:
_renderOffers: function(key) {
var details = this.props[key];
return (
/* Add a key prop here. E.g. details.itemId */
<li key={details.itemId} className="offer-of-item">
€ {details.price}
<a className="remove-offer" onClick={this._removeOffer}>X</a>
</li>
)
},
This makes sure that when you add or remove items, or alter the data, React only manipulates the corresponding DOM element, instead of redrawing the entire list, or potentially alter the wrong element.
Related
Example: https://jsfiddle.net/wbellman/ghuw2ers/6/
In an application I am working on, I have a parent container (List, in my example) that contains a list of children (Hero, in my example). The list is governed by an outside object. For simplicity I declared the object directly in the JS. (In my real application the data store is properly namespaced and so forth.)
The problem I have is in the list I have three elements, if I remove an item from the middle, the rendered list appears to remove the last element. However the outside object reflects the proper list.
For example:
My list has the elements: cap, thor, hulk
If you remove "thor", "cap" and "thor" are rendered
The heroList reflects "cap" and "hulk" as it should
I am relatively new to ReactJs, so there is a good chance my premise is fundamentally flawed.
Note: The example reflects a much more complex application. It's structured similarly for purposes of demonstration. I am aware you could make a single component, but it would not be practical in the actual app.
Any help would be appreciated.
Here is the code from JSFiddle:
var heroList = [
{ name: "cap" },
{ name: "thor"},
{ name: "hulk"}
];
var List = React.createClass({
getInitialState() {
console.log("heros", heroList);
return {
heros: heroList
};
},
onChange(e){
this.setState({heros: heroList});
},
removeHero(i,heros){
var hero = heros[i];
console.log("removing hero...", hero);
heroList = _.filter(heroList, function(h){ return h.name !== hero.name;});
this.setState({heros:heroList});
},
render() {
var heros = this.state.heros;
var createHero = (hero,index) => {
return <Hero hero={hero} key={index} onRemove={this.removeHero.bind(this,index,heros)}/>;
};
console.log("list", heros);
return (
<ul>
{heros.map(createHero)}
</ul>
)
}
});
var Hero = React.createClass({
getInitialState() {
return {
hero: this.props.hero
}
},
render() {
var hero = this.state.hero;
return (
<li>Hello {hero.name} | <button type="button" onClick={this.props.onRemove}>-</button></li>
);
}
});
ReactDOM.render(
<List />,
document.getElementById('container')
);
Additional: I was having problems copying the code from JSFiddle, anything I broke by accident should work in the JSFiddle listed at the top of this question.
Edit:
Based on the commentary from madox2, nicole, nuway and Damien Leroux, here's what I ended up doing:
https://jsfiddle.net/wbellman/ghuw2ers/10/
I wish there was a way to give everyone credit, you were all a big help.
Changing your Hero class to this fixed the issue of displaying the wrong hero name for me:
var Hero = React.createClass({
render() {
return (
<li>Hello {this.props.hero.name} | <button type="button" onClick={this.props.onRemove}>-</button></li>
);
}
});
i.e. I removed the local state from the class and used the prop directly.
Generally speaking, try to use the local store only when you really need it. Try to think of your components as stateless, i.e. they get something through the props and display it, that's it.
Along these lines, you should consider passing the hero list through the props to your List component as well.
if you really have problems with managing your data you should use Flux or Redux.
in this code:
heroList = _.filter(heroList, function(h){ return h.name !== hero.name;});
i just dont get why you filer the heroList instead of this.state.heros? every time you add or remove a hero, the heroList in your current scope shouldnt be kept in state? the global heroList is just the initial state.
The problem is with the keys used. Since the key is taken from the index, that key has already been used and thus the hero with that key is shown.
change it to key={Math.random() * 100} and it will work
I have a component that receives offers and want to set the highest on top. This is the code:
var Offerslist = React.createClass({
_renderOffers: function(key) {
var details = this.props[key];
// TODO: set order of highest offer descending
return (
<li key={key} className="offer-of-item">
€ {details.price}
<a className="remove-offer" onClick={this._removeOffer}>X</a>
</li>
)
},
render : function() {
return (
<ol className="list-of-offers-per-item">
{Object.keys(this.props).map(this._renderOffers)}
</ol>
)
}
});
I thought of populating an array with objects {price:123, offerid:'foo123'} and then use .sort by price. But I can't populate an array like this as it never iterates through all objects at once. Is there perhaps some built-in React way to do this?
data:
For a ordinary array you'd use the sort method. But since your offers is an object, not an array, you can sort the keys by looking up the price in the offers object like this:
Object.keys(this.props)
.sort((a, b) => ( this.props[a].price - this.props[b].price ))
.map( this._renderOffers)
I'm learning Reactjs and tried to copy the example from this facebook git, children to make the warning disappear, but I'm still getting it:
var MyComponent = React.createClass({
getInitialState: function () {
return {idx: 0};
},
render: function() {
var results = this.props.results;
return (
<ol>
{results.map(function(result) {
return <li key={result.id}>{result.book}</li>;
})}
</ol>
);
}
});
var result = [
{title: "book", content: "booky"},
{title: "pen", content: "penny"},
];
document.addEventListener("DOMContentLoaded", function () {
ReactDOM.render(<MyComponent results={results} />, document.getElementById('widgets'));
});
A few things going on here that need to be rectified:
The variable you're declaring and assigning the array of results to is named result but you are passing a variable named results as the results prop to MyComponent. Rename this variable to results.
The two object properties you're attempting to access on the result object inside the map function are id and book, neither of which are defined on the object itself (title and content are). Change {result.book} to {result.title} in this case.
Finally, in order to supply a unique ID to each element returned from the map, set the second parameter of your map (e.g. results.map(function(result, i) {... and use the array index as your unique key.
In summary:
results.map(function(result, i) {
return <li key={i}>{result.title}</li>;
})
I'm a beginner of ReactJS and I'm stuck trying to figure out why map only returns a single prop at a time.
In file1.jsx, I have an array that contains 3 objects:
var MainPanelOneData = [{"id":1,"shotviews":15080},{"id":2,"likes":12000},{"id":3,"comments":5100}];
File1.jsx also has a render function to extrapolate the data inside the array:
render: function() {
var ListMainPanelOne = MainPanelOneData.map(function(data) {
return <MainPanelOne key={data.key} shotviews={data.shotviews} likes={data.likes} comments={data.comments} />
});
In file2.jsx, I have this code to render the data object from file1.jsx:
render: function() {
return (
<div>
<span>{this.props.shotviews} shot views</span>
<span>{this.props.likes} likes</span>
<span>{this.props.comments} comments</span>
</div>
)
}
The result shows this:
15080 shot views likes comments
shot views12000 likes comments
shot views likes5100 comments
I'm guessing it repeats like this because it searches through one key at a time? If that's the case, how do I display all keys at the same time? Use indexing?
well your array of data doesnt have all the keys. each one of your objects in PanelOneData has ONE key and is missing the other two; additionally none of them have key called key. so youre making three MainPanelOne components, each with a single prop. the result of that map is this
[
<MainPanelOne key={null} shotviews={15080} likes={null} comments={null} />,
<MainPanelOne key={null} shotviews={null} likes={12000} comments={null} />,
<MainPanelOne key={null} shotviews={null} likes={null} comments={5100} />
]
which is an accurate display of what youre seeing
To get one line you might do something like this.
render: function() {
var ListMainPanelOne = MainPanelOneData.map(function(data) {
return <span key={data.id}> {data.shotviews} {data.likes} {data.comments} </span>
});
I have an ng-repeat like:
<div ng-repeat="(k, v) in query_p[$index].tags">
I would like it so that if the key (k) is a certain string, say "foo", that string always appears first in the list. It seems the getter option or orderBy only works with arrays. Anyone have an example of how they might achieve this?
Basically you have an unordered object, and you want it to have some kind of order.
To do that you need to create a function that returns some ordered object.
myApp.filter('promote_foo', function() {
return function(object, comp) {
console.log(object);
console.log(comp);
var ordered = [];
for (var key in object) {
var obj = {
key: key,
value: object[key]
};
if (key === comp)
ordered.splice(0,0,obj);
else
ordered.push(obj);
}
console.log(ordered);
return ordered;
};
});
the function takes a parameter which will promote and object if the key matches it. Now I only call it in the controller directly, but you could use it just like any angular filter.
$scope.order = $filter('promote_foo')($scope.data, 'foo');
Also, you can play with the fiddle here.
Hope this helped!