react counter for each item of list
up vote
1
down vote
favorite
I'm trying to create a counter for each item in a list in React. I want each to be incremented or decremented individually depending on what the user clicks on. This issue is that all counters increment and decrement on click
of a single element, but I would like only the clicked element's counter to change.
this is my code:
class name extends Component {
constructor(){
super()
this.state = {
news: ,
voteing: 0
}
}
onVoting(type){
this.setState(prevState => {
return {voteing: type == 'add' ? prevState.voteing + 1: prevState.voteing- 1}
});
}
render() {
return (
<React.Fragment>
<Content>
{
this.state.news.map((item, i)=>{
return (
<Item key={i}>
<text>
{item.subject}
{item.details}
</text>
<Votering>
<img src="" onClick={this.onVoting.bind(this, 'add')} />
<div value={this.state.voteing}>{this.state.voteing}</div>
<img src="" onClick={this.onVoting.bind(this, 'min')} />
</Votering>
</Item>
)
})
}
</Content>
</React.Fragment>
)
}
}
I'm trying to do this:
<img src="" onClick={this.onVote(i).bind(this, 'add')} />
but it doesn't work also tried this.onVote(item.i)
and same result
reactjs
add a comment |
up vote
1
down vote
favorite
I'm trying to create a counter for each item in a list in React. I want each to be incremented or decremented individually depending on what the user clicks on. This issue is that all counters increment and decrement on click
of a single element, but I would like only the clicked element's counter to change.
this is my code:
class name extends Component {
constructor(){
super()
this.state = {
news: ,
voteing: 0
}
}
onVoting(type){
this.setState(prevState => {
return {voteing: type == 'add' ? prevState.voteing + 1: prevState.voteing- 1}
});
}
render() {
return (
<React.Fragment>
<Content>
{
this.state.news.map((item, i)=>{
return (
<Item key={i}>
<text>
{item.subject}
{item.details}
</text>
<Votering>
<img src="" onClick={this.onVoting.bind(this, 'add')} />
<div value={this.state.voteing}>{this.state.voteing}</div>
<img src="" onClick={this.onVoting.bind(this, 'min')} />
</Votering>
</Item>
)
})
}
</Content>
</React.Fragment>
)
}
}
I'm trying to do this:
<img src="" onClick={this.onVote(i).bind(this, 'add')} />
but it doesn't work also tried this.onVote(item.i)
and same result
reactjs
could I get a sample of what the news array looks like?
– joshua fermin
Nov 19 at 23:06
I'm fetch data from api so is the link of this api will be enough for you? @joshua fermin
– GNDevs
Nov 19 at 23:08
The issue is thatvoteing
is the only thing you're tracking, a single value in the state, that you're sharing between all iterations of yourmap
. Also, it's just "voting", no e
– AnonymousSB
Nov 19 at 23:18
add a comment |
up vote
1
down vote
favorite
up vote
1
down vote
favorite
I'm trying to create a counter for each item in a list in React. I want each to be incremented or decremented individually depending on what the user clicks on. This issue is that all counters increment and decrement on click
of a single element, but I would like only the clicked element's counter to change.
this is my code:
class name extends Component {
constructor(){
super()
this.state = {
news: ,
voteing: 0
}
}
onVoting(type){
this.setState(prevState => {
return {voteing: type == 'add' ? prevState.voteing + 1: prevState.voteing- 1}
});
}
render() {
return (
<React.Fragment>
<Content>
{
this.state.news.map((item, i)=>{
return (
<Item key={i}>
<text>
{item.subject}
{item.details}
</text>
<Votering>
<img src="" onClick={this.onVoting.bind(this, 'add')} />
<div value={this.state.voteing}>{this.state.voteing}</div>
<img src="" onClick={this.onVoting.bind(this, 'min')} />
</Votering>
</Item>
)
})
}
</Content>
</React.Fragment>
)
}
}
I'm trying to do this:
<img src="" onClick={this.onVote(i).bind(this, 'add')} />
but it doesn't work also tried this.onVote(item.i)
and same result
reactjs
I'm trying to create a counter for each item in a list in React. I want each to be incremented or decremented individually depending on what the user clicks on. This issue is that all counters increment and decrement on click
of a single element, but I would like only the clicked element's counter to change.
this is my code:
class name extends Component {
constructor(){
super()
this.state = {
news: ,
voteing: 0
}
}
onVoting(type){
this.setState(prevState => {
return {voteing: type == 'add' ? prevState.voteing + 1: prevState.voteing- 1}
});
}
render() {
return (
<React.Fragment>
<Content>
{
this.state.news.map((item, i)=>{
return (
<Item key={i}>
<text>
{item.subject}
{item.details}
</text>
<Votering>
<img src="" onClick={this.onVoting.bind(this, 'add')} />
<div value={this.state.voteing}>{this.state.voteing}</div>
<img src="" onClick={this.onVoting.bind(this, 'min')} />
</Votering>
</Item>
)
})
}
</Content>
</React.Fragment>
)
}
}
I'm trying to do this:
<img src="" onClick={this.onVote(i).bind(this, 'add')} />
but it doesn't work also tried this.onVote(item.i)
and same result
reactjs
reactjs
edited Nov 19 at 23:11
asked Nov 19 at 23:00
GNDevs
15310
15310
could I get a sample of what the news array looks like?
– joshua fermin
Nov 19 at 23:06
I'm fetch data from api so is the link of this api will be enough for you? @joshua fermin
– GNDevs
Nov 19 at 23:08
The issue is thatvoteing
is the only thing you're tracking, a single value in the state, that you're sharing between all iterations of yourmap
. Also, it's just "voting", no e
– AnonymousSB
Nov 19 at 23:18
add a comment |
could I get a sample of what the news array looks like?
– joshua fermin
Nov 19 at 23:06
I'm fetch data from api so is the link of this api will be enough for you? @joshua fermin
– GNDevs
Nov 19 at 23:08
The issue is thatvoteing
is the only thing you're tracking, a single value in the state, that you're sharing between all iterations of yourmap
. Also, it's just "voting", no e
– AnonymousSB
Nov 19 at 23:18
could I get a sample of what the news array looks like?
– joshua fermin
Nov 19 at 23:06
could I get a sample of what the news array looks like?
– joshua fermin
Nov 19 at 23:06
I'm fetch data from api so is the link of this api will be enough for you? @joshua fermin
– GNDevs
Nov 19 at 23:08
I'm fetch data from api so is the link of this api will be enough for you? @joshua fermin
– GNDevs
Nov 19 at 23:08
The issue is that
voteing
is the only thing you're tracking, a single value in the state, that you're sharing between all iterations of your map
. Also, it's just "voting", no e– AnonymousSB
Nov 19 at 23:18
The issue is that
voteing
is the only thing you're tracking, a single value in the state, that you're sharing between all iterations of your map
. Also, it's just "voting", no e– AnonymousSB
Nov 19 at 23:18
add a comment |
3 Answers
3
active
oldest
votes
up vote
1
down vote
accepted
The reason all your items' counts change when any of them is clicked on is that they all share the same vote count value, voteing
in the name
component's state.
To fix this, you should break each item into its own stateful component. So that each can track its own click count.
For example:
class name extends Component {
constructor(){
super();
this.state = {
news:
}
}
render() {
return (
<React.Fragment>
<Content>
{
this.state.news.map((item, i) => {
return <NewsItem key={ i }
subject={ item.subject }
details={ item.details }
/>
})
}
</Content>
</React.Fragment>
)
}
}
class NewsItem extends Component {
constructor() {
super();
this.state = {
voteCount = 0
}
}
handleVote(type) {
this.setState(prevState => ({
voteCount: type === "add" ? prevState.voteCount + 1 : prevState.voteCount - 1
}));
}
render() {
const { subject, details } = this.props;
const { voteCount } = this.state;
return (
<Item>
<text>
{ subject }
{ details }
</text>
<Votering>
<img src="" onClick={ this.handleVote.bind(this, 'add') } />
<div value={ voteCount }>{ voteCount }</div>
<img src="" onClick={ this.handleVote.bind(this, 'min') } />
</Votering>
</Item>
)
}
}
You could also maintain separate counts for each item within the parent component, but I find breaking into separate components to be much cleaner.
one would wonder if it should be component state, or simply part of the entity, but okay that much info is not really available :)
– Icepickle
Nov 19 at 23:23
okay I understand the issue now but I couldn't understand how can keep separates counts within the name component can you give me an example please @Henry Woody
– GNDevs
Nov 19 at 23:30
1
@GNDevs I would really recommend against doing it that way because it's messy and easy to get bugs. But if you really want to, you'll have to changethis.state.voteing
to an array of vote counts that correspond to the news items by index. Then on click, update count at the correct index.
– Henry Woody
Nov 19 at 23:36
add a comment |
up vote
2
down vote
I cannot really see how you would like to see voting as part of the local component state, as it really has to do (in my opinion), with the entities on which you can vote.
So if I were you, I would rewrite the code slightly different. As I do not know what you intend to do afterwards with the votes (this rather assumes like a live process, or at least a kind of save button, as it is saved here in the local VotingApp
state), I just save everything to the local state, how you would handle that is not really my intend to answer.
So personally, I would rather go for one functional component, just rendering the news item and it's voting capability, where the voteCount
is part of the item entity. If this is not how you receive the data, nothing stops you from adding the data after your fetch and before really showing it on the screen. The app itself will receive the changes and the item that will be changed, and what it does there-after, would be all up to you ;)
const { Component } = React;
const NewsItem = ( item ) => {
const { subject, details, voteCount, handleVoteChange } = item;
return (
<div className="news-item">
<div className="news-vote">
<div className="vote-up" title="Vote up" onClick={ () => handleVoteChange( item, 1 ) }></div>
<div className="vote-count">{ voteCount }</div>
<div className="vote-down" title="Vote down" onClick={ () => handleVoteChange( item, -1 ) }></div>
</div>
<div className="news-content">
<h3>{ subject }</h3>
<div>{ details }</div>
</div>
</div>
);
};
class VotingApp extends Component {
constructor( props ) {
super();
this.handleVoteChange = this.handleVoteChange.bind( this );
// by lack of fetching I add the initial newsItems to the state
// and work by updating local state on voteChanges
// depending on your state management (I guess you want to do something with the votes)
// you could change this
this.state = {
newsItems: props.newsItems
};
}
handleVoteChange( item, increment ) {
this.setState( ( prevState ) => {
const { newsItems } = prevState;
// updates only the single item that has changed
return {
newsItems: newsItems
.map( oldItem => oldItem.id === item.id ?
{ ...oldItem, voteCount: oldItem.voteCount + increment } :
oldItem ) };
} );
}
render() {
const { newsItems = } = this.state;
return (
<div className="kiosk">
{ newsItems.map( item => <NewsItem
key={ item.id }
{...item}
handleVoteChange={this.handleVoteChange} /> ) }
</div>
);
}
}
// some bogus news items
const newsItems = [
{ id: 1, voteCount: 0, subject: 'Mars in 2020', details: 'Tesla will send manned BFR rockets to Mars in 2020' },
{ id: 2, voteCount: -3, subject: 'Stackoverflow rocks', details: 'Stackoverflow is booming thanks to the new friendly policy' },
{ id: 3, voteCount: 10, subject: 'DS9: Healthy living', details: 'Eat rice everyday and drink only water, and live 10 years longer, says Dax to Sisko, Sisko suprises her by saying that like that, he doesn't want to live 10 years longer...' }
];
// render towards the container
const target = document.querySelector('#container');
ReactDOM.render( <VotingApp newsItems={ newsItems } />, target );
.kiosk {
display: flex;
flex-wrap: no-wrap;
}
.news-item {
display: flex;
justify-content: flex-start;
width: 100%;
}
.news-vote {
display: flex;
flex-direction: column;
align-items: center;
padding-left: 10px;
padding-right: 10px;
}
.news-vote > * {
cursor: pointer;
}
.news-content {
display: flex;
flex-direction: column;
}
.vote-up::before {
content: '▲';
}
.vote-down::before {
content: '▼';
}
.vote-up:hover, .vote-down:hover {
color: #cfcfcf;
}
h3 { margin: 0; }
<script id="react" src="https://cdnjs.cloudflare.com/ajax/libs/react/15.6.2/react.js"></script>
<script id="react-dom" src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/15.6.2/react-dom.js"></script>
<script id="prop-types" src="https://cdnjs.cloudflare.com/ajax/libs/prop-types/15.6.0/prop-types.js"></script>
<script id="classnames" src="https://cdnjs.cloudflare.com/ajax/libs/classnames/2.2.5/index.js"></script>
<div id="container"></div>
add a comment |
up vote
1
down vote
A few things I noticed unrelated to your question.
1) onVoting
should be bound in your constructor or use onVoting = () => { ..... }
2) in your render function you have onVote
instead of onVoting
On to your main question, in your state you are only maintaining one counter that is displayed and changed for all news elements. an easy way to get around this is to create a new react element for each news article that will handle the voting for each article.
class parent extends Component {
constructor(){
super()
this.state = {
news: null,
}
}
componentDidMount() {
// fetch data from api and minipulate as needed
this.setState({news: dataFromApi})
}
render() {
return (
<Content>
{
this.state.news.map((item, i)=>{
return (
<NewChildComponent data={item}/>
)
})
}
</Content>
)
}
}
class NewChildComponent extends Component {
constructor() {
super()
this.state = {
voting: 0,
}
}
onVoting = (e) => {
this.setState(prevState => ({
voteCount: e.target.name === "add" ? prevState.voteCount + 1 : prevState.voteCount - 1
}));
}
render () {
const {data} = this.props;
return (
<Item key={data.uniqueID}>
<text>
{data.subject}
{data.details}
</text>
<Votering>
<img src="" onClick={this.onVoting} name="add"/>
<div value={this.state.voteing}>{this.state.voteing}</div>
<img src="" onClick={this.onVoting} name="min"/>
</Votering>
</Item>
)
}
}
A little background on why you should not bind in your render function. https://medium.freecodecamp.org/why-arrow-functions-and-bind-in-reacts-render-are-problematic-f1c08b060e36
Here’s why: The parent component is passing down an arrow function on
props. Arrow functions are reallocated on every render (same story
with using bind). So although I’ve declared User.js as a
PureComponent, the arrow function in User’s parent causes the User
component to see a new function being sent in on props for all users.
So every user re-renders when any delete button is clicked. 👎
Also why you should not use an index as a key in React.
https://reactjs.org/docs/lists-and-keys.html
We don’t recommend using indexes for keys if the order of items may
change. This can negatively impact performance and may cause issues
with component state. Check out Robin Pokorny’s article for an
in-depth explanation on the negative impacts of using an index as a
key. If you choose not to assign an explicit key to list items then
React will default to using indexes as keys.
Here is an in-depth explanation about why keys are necessary if you’re
interested in learning more.
add a comment |
3 Answers
3
active
oldest
votes
3 Answers
3
active
oldest
votes
active
oldest
votes
active
oldest
votes
up vote
1
down vote
accepted
The reason all your items' counts change when any of them is clicked on is that they all share the same vote count value, voteing
in the name
component's state.
To fix this, you should break each item into its own stateful component. So that each can track its own click count.
For example:
class name extends Component {
constructor(){
super();
this.state = {
news:
}
}
render() {
return (
<React.Fragment>
<Content>
{
this.state.news.map((item, i) => {
return <NewsItem key={ i }
subject={ item.subject }
details={ item.details }
/>
})
}
</Content>
</React.Fragment>
)
}
}
class NewsItem extends Component {
constructor() {
super();
this.state = {
voteCount = 0
}
}
handleVote(type) {
this.setState(prevState => ({
voteCount: type === "add" ? prevState.voteCount + 1 : prevState.voteCount - 1
}));
}
render() {
const { subject, details } = this.props;
const { voteCount } = this.state;
return (
<Item>
<text>
{ subject }
{ details }
</text>
<Votering>
<img src="" onClick={ this.handleVote.bind(this, 'add') } />
<div value={ voteCount }>{ voteCount }</div>
<img src="" onClick={ this.handleVote.bind(this, 'min') } />
</Votering>
</Item>
)
}
}
You could also maintain separate counts for each item within the parent component, but I find breaking into separate components to be much cleaner.
one would wonder if it should be component state, or simply part of the entity, but okay that much info is not really available :)
– Icepickle
Nov 19 at 23:23
okay I understand the issue now but I couldn't understand how can keep separates counts within the name component can you give me an example please @Henry Woody
– GNDevs
Nov 19 at 23:30
1
@GNDevs I would really recommend against doing it that way because it's messy and easy to get bugs. But if you really want to, you'll have to changethis.state.voteing
to an array of vote counts that correspond to the news items by index. Then on click, update count at the correct index.
– Henry Woody
Nov 19 at 23:36
add a comment |
up vote
1
down vote
accepted
The reason all your items' counts change when any of them is clicked on is that they all share the same vote count value, voteing
in the name
component's state.
To fix this, you should break each item into its own stateful component. So that each can track its own click count.
For example:
class name extends Component {
constructor(){
super();
this.state = {
news:
}
}
render() {
return (
<React.Fragment>
<Content>
{
this.state.news.map((item, i) => {
return <NewsItem key={ i }
subject={ item.subject }
details={ item.details }
/>
})
}
</Content>
</React.Fragment>
)
}
}
class NewsItem extends Component {
constructor() {
super();
this.state = {
voteCount = 0
}
}
handleVote(type) {
this.setState(prevState => ({
voteCount: type === "add" ? prevState.voteCount + 1 : prevState.voteCount - 1
}));
}
render() {
const { subject, details } = this.props;
const { voteCount } = this.state;
return (
<Item>
<text>
{ subject }
{ details }
</text>
<Votering>
<img src="" onClick={ this.handleVote.bind(this, 'add') } />
<div value={ voteCount }>{ voteCount }</div>
<img src="" onClick={ this.handleVote.bind(this, 'min') } />
</Votering>
</Item>
)
}
}
You could also maintain separate counts for each item within the parent component, but I find breaking into separate components to be much cleaner.
one would wonder if it should be component state, or simply part of the entity, but okay that much info is not really available :)
– Icepickle
Nov 19 at 23:23
okay I understand the issue now but I couldn't understand how can keep separates counts within the name component can you give me an example please @Henry Woody
– GNDevs
Nov 19 at 23:30
1
@GNDevs I would really recommend against doing it that way because it's messy and easy to get bugs. But if you really want to, you'll have to changethis.state.voteing
to an array of vote counts that correspond to the news items by index. Then on click, update count at the correct index.
– Henry Woody
Nov 19 at 23:36
add a comment |
up vote
1
down vote
accepted
up vote
1
down vote
accepted
The reason all your items' counts change when any of them is clicked on is that they all share the same vote count value, voteing
in the name
component's state.
To fix this, you should break each item into its own stateful component. So that each can track its own click count.
For example:
class name extends Component {
constructor(){
super();
this.state = {
news:
}
}
render() {
return (
<React.Fragment>
<Content>
{
this.state.news.map((item, i) => {
return <NewsItem key={ i }
subject={ item.subject }
details={ item.details }
/>
})
}
</Content>
</React.Fragment>
)
}
}
class NewsItem extends Component {
constructor() {
super();
this.state = {
voteCount = 0
}
}
handleVote(type) {
this.setState(prevState => ({
voteCount: type === "add" ? prevState.voteCount + 1 : prevState.voteCount - 1
}));
}
render() {
const { subject, details } = this.props;
const { voteCount } = this.state;
return (
<Item>
<text>
{ subject }
{ details }
</text>
<Votering>
<img src="" onClick={ this.handleVote.bind(this, 'add') } />
<div value={ voteCount }>{ voteCount }</div>
<img src="" onClick={ this.handleVote.bind(this, 'min') } />
</Votering>
</Item>
)
}
}
You could also maintain separate counts for each item within the parent component, but I find breaking into separate components to be much cleaner.
The reason all your items' counts change when any of them is clicked on is that they all share the same vote count value, voteing
in the name
component's state.
To fix this, you should break each item into its own stateful component. So that each can track its own click count.
For example:
class name extends Component {
constructor(){
super();
this.state = {
news:
}
}
render() {
return (
<React.Fragment>
<Content>
{
this.state.news.map((item, i) => {
return <NewsItem key={ i }
subject={ item.subject }
details={ item.details }
/>
})
}
</Content>
</React.Fragment>
)
}
}
class NewsItem extends Component {
constructor() {
super();
this.state = {
voteCount = 0
}
}
handleVote(type) {
this.setState(prevState => ({
voteCount: type === "add" ? prevState.voteCount + 1 : prevState.voteCount - 1
}));
}
render() {
const { subject, details } = this.props;
const { voteCount } = this.state;
return (
<Item>
<text>
{ subject }
{ details }
</text>
<Votering>
<img src="" onClick={ this.handleVote.bind(this, 'add') } />
<div value={ voteCount }>{ voteCount }</div>
<img src="" onClick={ this.handleVote.bind(this, 'min') } />
</Votering>
</Item>
)
}
}
You could also maintain separate counts for each item within the parent component, but I find breaking into separate components to be much cleaner.
answered Nov 19 at 23:20
Henry Woody
3,4562824
3,4562824
one would wonder if it should be component state, or simply part of the entity, but okay that much info is not really available :)
– Icepickle
Nov 19 at 23:23
okay I understand the issue now but I couldn't understand how can keep separates counts within the name component can you give me an example please @Henry Woody
– GNDevs
Nov 19 at 23:30
1
@GNDevs I would really recommend against doing it that way because it's messy and easy to get bugs. But if you really want to, you'll have to changethis.state.voteing
to an array of vote counts that correspond to the news items by index. Then on click, update count at the correct index.
– Henry Woody
Nov 19 at 23:36
add a comment |
one would wonder if it should be component state, or simply part of the entity, but okay that much info is not really available :)
– Icepickle
Nov 19 at 23:23
okay I understand the issue now but I couldn't understand how can keep separates counts within the name component can you give me an example please @Henry Woody
– GNDevs
Nov 19 at 23:30
1
@GNDevs I would really recommend against doing it that way because it's messy and easy to get bugs. But if you really want to, you'll have to changethis.state.voteing
to an array of vote counts that correspond to the news items by index. Then on click, update count at the correct index.
– Henry Woody
Nov 19 at 23:36
one would wonder if it should be component state, or simply part of the entity, but okay that much info is not really available :)
– Icepickle
Nov 19 at 23:23
one would wonder if it should be component state, or simply part of the entity, but okay that much info is not really available :)
– Icepickle
Nov 19 at 23:23
okay I understand the issue now but I couldn't understand how can keep separates counts within the name component can you give me an example please @Henry Woody
– GNDevs
Nov 19 at 23:30
okay I understand the issue now but I couldn't understand how can keep separates counts within the name component can you give me an example please @Henry Woody
– GNDevs
Nov 19 at 23:30
1
1
@GNDevs I would really recommend against doing it that way because it's messy and easy to get bugs. But if you really want to, you'll have to change
this.state.voteing
to an array of vote counts that correspond to the news items by index. Then on click, update count at the correct index.– Henry Woody
Nov 19 at 23:36
@GNDevs I would really recommend against doing it that way because it's messy and easy to get bugs. But if you really want to, you'll have to change
this.state.voteing
to an array of vote counts that correspond to the news items by index. Then on click, update count at the correct index.– Henry Woody
Nov 19 at 23:36
add a comment |
up vote
2
down vote
I cannot really see how you would like to see voting as part of the local component state, as it really has to do (in my opinion), with the entities on which you can vote.
So if I were you, I would rewrite the code slightly different. As I do not know what you intend to do afterwards with the votes (this rather assumes like a live process, or at least a kind of save button, as it is saved here in the local VotingApp
state), I just save everything to the local state, how you would handle that is not really my intend to answer.
So personally, I would rather go for one functional component, just rendering the news item and it's voting capability, where the voteCount
is part of the item entity. If this is not how you receive the data, nothing stops you from adding the data after your fetch and before really showing it on the screen. The app itself will receive the changes and the item that will be changed, and what it does there-after, would be all up to you ;)
const { Component } = React;
const NewsItem = ( item ) => {
const { subject, details, voteCount, handleVoteChange } = item;
return (
<div className="news-item">
<div className="news-vote">
<div className="vote-up" title="Vote up" onClick={ () => handleVoteChange( item, 1 ) }></div>
<div className="vote-count">{ voteCount }</div>
<div className="vote-down" title="Vote down" onClick={ () => handleVoteChange( item, -1 ) }></div>
</div>
<div className="news-content">
<h3>{ subject }</h3>
<div>{ details }</div>
</div>
</div>
);
};
class VotingApp extends Component {
constructor( props ) {
super();
this.handleVoteChange = this.handleVoteChange.bind( this );
// by lack of fetching I add the initial newsItems to the state
// and work by updating local state on voteChanges
// depending on your state management (I guess you want to do something with the votes)
// you could change this
this.state = {
newsItems: props.newsItems
};
}
handleVoteChange( item, increment ) {
this.setState( ( prevState ) => {
const { newsItems } = prevState;
// updates only the single item that has changed
return {
newsItems: newsItems
.map( oldItem => oldItem.id === item.id ?
{ ...oldItem, voteCount: oldItem.voteCount + increment } :
oldItem ) };
} );
}
render() {
const { newsItems = } = this.state;
return (
<div className="kiosk">
{ newsItems.map( item => <NewsItem
key={ item.id }
{...item}
handleVoteChange={this.handleVoteChange} /> ) }
</div>
);
}
}
// some bogus news items
const newsItems = [
{ id: 1, voteCount: 0, subject: 'Mars in 2020', details: 'Tesla will send manned BFR rockets to Mars in 2020' },
{ id: 2, voteCount: -3, subject: 'Stackoverflow rocks', details: 'Stackoverflow is booming thanks to the new friendly policy' },
{ id: 3, voteCount: 10, subject: 'DS9: Healthy living', details: 'Eat rice everyday and drink only water, and live 10 years longer, says Dax to Sisko, Sisko suprises her by saying that like that, he doesn't want to live 10 years longer...' }
];
// render towards the container
const target = document.querySelector('#container');
ReactDOM.render( <VotingApp newsItems={ newsItems } />, target );
.kiosk {
display: flex;
flex-wrap: no-wrap;
}
.news-item {
display: flex;
justify-content: flex-start;
width: 100%;
}
.news-vote {
display: flex;
flex-direction: column;
align-items: center;
padding-left: 10px;
padding-right: 10px;
}
.news-vote > * {
cursor: pointer;
}
.news-content {
display: flex;
flex-direction: column;
}
.vote-up::before {
content: '▲';
}
.vote-down::before {
content: '▼';
}
.vote-up:hover, .vote-down:hover {
color: #cfcfcf;
}
h3 { margin: 0; }
<script id="react" src="https://cdnjs.cloudflare.com/ajax/libs/react/15.6.2/react.js"></script>
<script id="react-dom" src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/15.6.2/react-dom.js"></script>
<script id="prop-types" src="https://cdnjs.cloudflare.com/ajax/libs/prop-types/15.6.0/prop-types.js"></script>
<script id="classnames" src="https://cdnjs.cloudflare.com/ajax/libs/classnames/2.2.5/index.js"></script>
<div id="container"></div>
add a comment |
up vote
2
down vote
I cannot really see how you would like to see voting as part of the local component state, as it really has to do (in my opinion), with the entities on which you can vote.
So if I were you, I would rewrite the code slightly different. As I do not know what you intend to do afterwards with the votes (this rather assumes like a live process, or at least a kind of save button, as it is saved here in the local VotingApp
state), I just save everything to the local state, how you would handle that is not really my intend to answer.
So personally, I would rather go for one functional component, just rendering the news item and it's voting capability, where the voteCount
is part of the item entity. If this is not how you receive the data, nothing stops you from adding the data after your fetch and before really showing it on the screen. The app itself will receive the changes and the item that will be changed, and what it does there-after, would be all up to you ;)
const { Component } = React;
const NewsItem = ( item ) => {
const { subject, details, voteCount, handleVoteChange } = item;
return (
<div className="news-item">
<div className="news-vote">
<div className="vote-up" title="Vote up" onClick={ () => handleVoteChange( item, 1 ) }></div>
<div className="vote-count">{ voteCount }</div>
<div className="vote-down" title="Vote down" onClick={ () => handleVoteChange( item, -1 ) }></div>
</div>
<div className="news-content">
<h3>{ subject }</h3>
<div>{ details }</div>
</div>
</div>
);
};
class VotingApp extends Component {
constructor( props ) {
super();
this.handleVoteChange = this.handleVoteChange.bind( this );
// by lack of fetching I add the initial newsItems to the state
// and work by updating local state on voteChanges
// depending on your state management (I guess you want to do something with the votes)
// you could change this
this.state = {
newsItems: props.newsItems
};
}
handleVoteChange( item, increment ) {
this.setState( ( prevState ) => {
const { newsItems } = prevState;
// updates only the single item that has changed
return {
newsItems: newsItems
.map( oldItem => oldItem.id === item.id ?
{ ...oldItem, voteCount: oldItem.voteCount + increment } :
oldItem ) };
} );
}
render() {
const { newsItems = } = this.state;
return (
<div className="kiosk">
{ newsItems.map( item => <NewsItem
key={ item.id }
{...item}
handleVoteChange={this.handleVoteChange} /> ) }
</div>
);
}
}
// some bogus news items
const newsItems = [
{ id: 1, voteCount: 0, subject: 'Mars in 2020', details: 'Tesla will send manned BFR rockets to Mars in 2020' },
{ id: 2, voteCount: -3, subject: 'Stackoverflow rocks', details: 'Stackoverflow is booming thanks to the new friendly policy' },
{ id: 3, voteCount: 10, subject: 'DS9: Healthy living', details: 'Eat rice everyday and drink only water, and live 10 years longer, says Dax to Sisko, Sisko suprises her by saying that like that, he doesn't want to live 10 years longer...' }
];
// render towards the container
const target = document.querySelector('#container');
ReactDOM.render( <VotingApp newsItems={ newsItems } />, target );
.kiosk {
display: flex;
flex-wrap: no-wrap;
}
.news-item {
display: flex;
justify-content: flex-start;
width: 100%;
}
.news-vote {
display: flex;
flex-direction: column;
align-items: center;
padding-left: 10px;
padding-right: 10px;
}
.news-vote > * {
cursor: pointer;
}
.news-content {
display: flex;
flex-direction: column;
}
.vote-up::before {
content: '▲';
}
.vote-down::before {
content: '▼';
}
.vote-up:hover, .vote-down:hover {
color: #cfcfcf;
}
h3 { margin: 0; }
<script id="react" src="https://cdnjs.cloudflare.com/ajax/libs/react/15.6.2/react.js"></script>
<script id="react-dom" src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/15.6.2/react-dom.js"></script>
<script id="prop-types" src="https://cdnjs.cloudflare.com/ajax/libs/prop-types/15.6.0/prop-types.js"></script>
<script id="classnames" src="https://cdnjs.cloudflare.com/ajax/libs/classnames/2.2.5/index.js"></script>
<div id="container"></div>
add a comment |
up vote
2
down vote
up vote
2
down vote
I cannot really see how you would like to see voting as part of the local component state, as it really has to do (in my opinion), with the entities on which you can vote.
So if I were you, I would rewrite the code slightly different. As I do not know what you intend to do afterwards with the votes (this rather assumes like a live process, or at least a kind of save button, as it is saved here in the local VotingApp
state), I just save everything to the local state, how you would handle that is not really my intend to answer.
So personally, I would rather go for one functional component, just rendering the news item and it's voting capability, where the voteCount
is part of the item entity. If this is not how you receive the data, nothing stops you from adding the data after your fetch and before really showing it on the screen. The app itself will receive the changes and the item that will be changed, and what it does there-after, would be all up to you ;)
const { Component } = React;
const NewsItem = ( item ) => {
const { subject, details, voteCount, handleVoteChange } = item;
return (
<div className="news-item">
<div className="news-vote">
<div className="vote-up" title="Vote up" onClick={ () => handleVoteChange( item, 1 ) }></div>
<div className="vote-count">{ voteCount }</div>
<div className="vote-down" title="Vote down" onClick={ () => handleVoteChange( item, -1 ) }></div>
</div>
<div className="news-content">
<h3>{ subject }</h3>
<div>{ details }</div>
</div>
</div>
);
};
class VotingApp extends Component {
constructor( props ) {
super();
this.handleVoteChange = this.handleVoteChange.bind( this );
// by lack of fetching I add the initial newsItems to the state
// and work by updating local state on voteChanges
// depending on your state management (I guess you want to do something with the votes)
// you could change this
this.state = {
newsItems: props.newsItems
};
}
handleVoteChange( item, increment ) {
this.setState( ( prevState ) => {
const { newsItems } = prevState;
// updates only the single item that has changed
return {
newsItems: newsItems
.map( oldItem => oldItem.id === item.id ?
{ ...oldItem, voteCount: oldItem.voteCount + increment } :
oldItem ) };
} );
}
render() {
const { newsItems = } = this.state;
return (
<div className="kiosk">
{ newsItems.map( item => <NewsItem
key={ item.id }
{...item}
handleVoteChange={this.handleVoteChange} /> ) }
</div>
);
}
}
// some bogus news items
const newsItems = [
{ id: 1, voteCount: 0, subject: 'Mars in 2020', details: 'Tesla will send manned BFR rockets to Mars in 2020' },
{ id: 2, voteCount: -3, subject: 'Stackoverflow rocks', details: 'Stackoverflow is booming thanks to the new friendly policy' },
{ id: 3, voteCount: 10, subject: 'DS9: Healthy living', details: 'Eat rice everyday and drink only water, and live 10 years longer, says Dax to Sisko, Sisko suprises her by saying that like that, he doesn't want to live 10 years longer...' }
];
// render towards the container
const target = document.querySelector('#container');
ReactDOM.render( <VotingApp newsItems={ newsItems } />, target );
.kiosk {
display: flex;
flex-wrap: no-wrap;
}
.news-item {
display: flex;
justify-content: flex-start;
width: 100%;
}
.news-vote {
display: flex;
flex-direction: column;
align-items: center;
padding-left: 10px;
padding-right: 10px;
}
.news-vote > * {
cursor: pointer;
}
.news-content {
display: flex;
flex-direction: column;
}
.vote-up::before {
content: '▲';
}
.vote-down::before {
content: '▼';
}
.vote-up:hover, .vote-down:hover {
color: #cfcfcf;
}
h3 { margin: 0; }
<script id="react" src="https://cdnjs.cloudflare.com/ajax/libs/react/15.6.2/react.js"></script>
<script id="react-dom" src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/15.6.2/react-dom.js"></script>
<script id="prop-types" src="https://cdnjs.cloudflare.com/ajax/libs/prop-types/15.6.0/prop-types.js"></script>
<script id="classnames" src="https://cdnjs.cloudflare.com/ajax/libs/classnames/2.2.5/index.js"></script>
<div id="container"></div>
I cannot really see how you would like to see voting as part of the local component state, as it really has to do (in my opinion), with the entities on which you can vote.
So if I were you, I would rewrite the code slightly different. As I do not know what you intend to do afterwards with the votes (this rather assumes like a live process, or at least a kind of save button, as it is saved here in the local VotingApp
state), I just save everything to the local state, how you would handle that is not really my intend to answer.
So personally, I would rather go for one functional component, just rendering the news item and it's voting capability, where the voteCount
is part of the item entity. If this is not how you receive the data, nothing stops you from adding the data after your fetch and before really showing it on the screen. The app itself will receive the changes and the item that will be changed, and what it does there-after, would be all up to you ;)
const { Component } = React;
const NewsItem = ( item ) => {
const { subject, details, voteCount, handleVoteChange } = item;
return (
<div className="news-item">
<div className="news-vote">
<div className="vote-up" title="Vote up" onClick={ () => handleVoteChange( item, 1 ) }></div>
<div className="vote-count">{ voteCount }</div>
<div className="vote-down" title="Vote down" onClick={ () => handleVoteChange( item, -1 ) }></div>
</div>
<div className="news-content">
<h3>{ subject }</h3>
<div>{ details }</div>
</div>
</div>
);
};
class VotingApp extends Component {
constructor( props ) {
super();
this.handleVoteChange = this.handleVoteChange.bind( this );
// by lack of fetching I add the initial newsItems to the state
// and work by updating local state on voteChanges
// depending on your state management (I guess you want to do something with the votes)
// you could change this
this.state = {
newsItems: props.newsItems
};
}
handleVoteChange( item, increment ) {
this.setState( ( prevState ) => {
const { newsItems } = prevState;
// updates only the single item that has changed
return {
newsItems: newsItems
.map( oldItem => oldItem.id === item.id ?
{ ...oldItem, voteCount: oldItem.voteCount + increment } :
oldItem ) };
} );
}
render() {
const { newsItems = } = this.state;
return (
<div className="kiosk">
{ newsItems.map( item => <NewsItem
key={ item.id }
{...item}
handleVoteChange={this.handleVoteChange} /> ) }
</div>
);
}
}
// some bogus news items
const newsItems = [
{ id: 1, voteCount: 0, subject: 'Mars in 2020', details: 'Tesla will send manned BFR rockets to Mars in 2020' },
{ id: 2, voteCount: -3, subject: 'Stackoverflow rocks', details: 'Stackoverflow is booming thanks to the new friendly policy' },
{ id: 3, voteCount: 10, subject: 'DS9: Healthy living', details: 'Eat rice everyday and drink only water, and live 10 years longer, says Dax to Sisko, Sisko suprises her by saying that like that, he doesn't want to live 10 years longer...' }
];
// render towards the container
const target = document.querySelector('#container');
ReactDOM.render( <VotingApp newsItems={ newsItems } />, target );
.kiosk {
display: flex;
flex-wrap: no-wrap;
}
.news-item {
display: flex;
justify-content: flex-start;
width: 100%;
}
.news-vote {
display: flex;
flex-direction: column;
align-items: center;
padding-left: 10px;
padding-right: 10px;
}
.news-vote > * {
cursor: pointer;
}
.news-content {
display: flex;
flex-direction: column;
}
.vote-up::before {
content: '▲';
}
.vote-down::before {
content: '▼';
}
.vote-up:hover, .vote-down:hover {
color: #cfcfcf;
}
h3 { margin: 0; }
<script id="react" src="https://cdnjs.cloudflare.com/ajax/libs/react/15.6.2/react.js"></script>
<script id="react-dom" src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/15.6.2/react-dom.js"></script>
<script id="prop-types" src="https://cdnjs.cloudflare.com/ajax/libs/prop-types/15.6.0/prop-types.js"></script>
<script id="classnames" src="https://cdnjs.cloudflare.com/ajax/libs/classnames/2.2.5/index.js"></script>
<div id="container"></div>
const { Component } = React;
const NewsItem = ( item ) => {
const { subject, details, voteCount, handleVoteChange } = item;
return (
<div className="news-item">
<div className="news-vote">
<div className="vote-up" title="Vote up" onClick={ () => handleVoteChange( item, 1 ) }></div>
<div className="vote-count">{ voteCount }</div>
<div className="vote-down" title="Vote down" onClick={ () => handleVoteChange( item, -1 ) }></div>
</div>
<div className="news-content">
<h3>{ subject }</h3>
<div>{ details }</div>
</div>
</div>
);
};
class VotingApp extends Component {
constructor( props ) {
super();
this.handleVoteChange = this.handleVoteChange.bind( this );
// by lack of fetching I add the initial newsItems to the state
// and work by updating local state on voteChanges
// depending on your state management (I guess you want to do something with the votes)
// you could change this
this.state = {
newsItems: props.newsItems
};
}
handleVoteChange( item, increment ) {
this.setState( ( prevState ) => {
const { newsItems } = prevState;
// updates only the single item that has changed
return {
newsItems: newsItems
.map( oldItem => oldItem.id === item.id ?
{ ...oldItem, voteCount: oldItem.voteCount + increment } :
oldItem ) };
} );
}
render() {
const { newsItems = } = this.state;
return (
<div className="kiosk">
{ newsItems.map( item => <NewsItem
key={ item.id }
{...item}
handleVoteChange={this.handleVoteChange} /> ) }
</div>
);
}
}
// some bogus news items
const newsItems = [
{ id: 1, voteCount: 0, subject: 'Mars in 2020', details: 'Tesla will send manned BFR rockets to Mars in 2020' },
{ id: 2, voteCount: -3, subject: 'Stackoverflow rocks', details: 'Stackoverflow is booming thanks to the new friendly policy' },
{ id: 3, voteCount: 10, subject: 'DS9: Healthy living', details: 'Eat rice everyday and drink only water, and live 10 years longer, says Dax to Sisko, Sisko suprises her by saying that like that, he doesn't want to live 10 years longer...' }
];
// render towards the container
const target = document.querySelector('#container');
ReactDOM.render( <VotingApp newsItems={ newsItems } />, target );
.kiosk {
display: flex;
flex-wrap: no-wrap;
}
.news-item {
display: flex;
justify-content: flex-start;
width: 100%;
}
.news-vote {
display: flex;
flex-direction: column;
align-items: center;
padding-left: 10px;
padding-right: 10px;
}
.news-vote > * {
cursor: pointer;
}
.news-content {
display: flex;
flex-direction: column;
}
.vote-up::before {
content: '▲';
}
.vote-down::before {
content: '▼';
}
.vote-up:hover, .vote-down:hover {
color: #cfcfcf;
}
h3 { margin: 0; }
<script id="react" src="https://cdnjs.cloudflare.com/ajax/libs/react/15.6.2/react.js"></script>
<script id="react-dom" src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/15.6.2/react-dom.js"></script>
<script id="prop-types" src="https://cdnjs.cloudflare.com/ajax/libs/prop-types/15.6.0/prop-types.js"></script>
<script id="classnames" src="https://cdnjs.cloudflare.com/ajax/libs/classnames/2.2.5/index.js"></script>
<div id="container"></div>
const { Component } = React;
const NewsItem = ( item ) => {
const { subject, details, voteCount, handleVoteChange } = item;
return (
<div className="news-item">
<div className="news-vote">
<div className="vote-up" title="Vote up" onClick={ () => handleVoteChange( item, 1 ) }></div>
<div className="vote-count">{ voteCount }</div>
<div className="vote-down" title="Vote down" onClick={ () => handleVoteChange( item, -1 ) }></div>
</div>
<div className="news-content">
<h3>{ subject }</h3>
<div>{ details }</div>
</div>
</div>
);
};
class VotingApp extends Component {
constructor( props ) {
super();
this.handleVoteChange = this.handleVoteChange.bind( this );
// by lack of fetching I add the initial newsItems to the state
// and work by updating local state on voteChanges
// depending on your state management (I guess you want to do something with the votes)
// you could change this
this.state = {
newsItems: props.newsItems
};
}
handleVoteChange( item, increment ) {
this.setState( ( prevState ) => {
const { newsItems } = prevState;
// updates only the single item that has changed
return {
newsItems: newsItems
.map( oldItem => oldItem.id === item.id ?
{ ...oldItem, voteCount: oldItem.voteCount + increment } :
oldItem ) };
} );
}
render() {
const { newsItems = } = this.state;
return (
<div className="kiosk">
{ newsItems.map( item => <NewsItem
key={ item.id }
{...item}
handleVoteChange={this.handleVoteChange} /> ) }
</div>
);
}
}
// some bogus news items
const newsItems = [
{ id: 1, voteCount: 0, subject: 'Mars in 2020', details: 'Tesla will send manned BFR rockets to Mars in 2020' },
{ id: 2, voteCount: -3, subject: 'Stackoverflow rocks', details: 'Stackoverflow is booming thanks to the new friendly policy' },
{ id: 3, voteCount: 10, subject: 'DS9: Healthy living', details: 'Eat rice everyday and drink only water, and live 10 years longer, says Dax to Sisko, Sisko suprises her by saying that like that, he doesn't want to live 10 years longer...' }
];
// render towards the container
const target = document.querySelector('#container');
ReactDOM.render( <VotingApp newsItems={ newsItems } />, target );
.kiosk {
display: flex;
flex-wrap: no-wrap;
}
.news-item {
display: flex;
justify-content: flex-start;
width: 100%;
}
.news-vote {
display: flex;
flex-direction: column;
align-items: center;
padding-left: 10px;
padding-right: 10px;
}
.news-vote > * {
cursor: pointer;
}
.news-content {
display: flex;
flex-direction: column;
}
.vote-up::before {
content: '▲';
}
.vote-down::before {
content: '▼';
}
.vote-up:hover, .vote-down:hover {
color: #cfcfcf;
}
h3 { margin: 0; }
<script id="react" src="https://cdnjs.cloudflare.com/ajax/libs/react/15.6.2/react.js"></script>
<script id="react-dom" src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/15.6.2/react-dom.js"></script>
<script id="prop-types" src="https://cdnjs.cloudflare.com/ajax/libs/prop-types/15.6.0/prop-types.js"></script>
<script id="classnames" src="https://cdnjs.cloudflare.com/ajax/libs/classnames/2.2.5/index.js"></script>
<div id="container"></div>
answered Nov 20 at 0:12
Icepickle
8,43232034
8,43232034
add a comment |
add a comment |
up vote
1
down vote
A few things I noticed unrelated to your question.
1) onVoting
should be bound in your constructor or use onVoting = () => { ..... }
2) in your render function you have onVote
instead of onVoting
On to your main question, in your state you are only maintaining one counter that is displayed and changed for all news elements. an easy way to get around this is to create a new react element for each news article that will handle the voting for each article.
class parent extends Component {
constructor(){
super()
this.state = {
news: null,
}
}
componentDidMount() {
// fetch data from api and minipulate as needed
this.setState({news: dataFromApi})
}
render() {
return (
<Content>
{
this.state.news.map((item, i)=>{
return (
<NewChildComponent data={item}/>
)
})
}
</Content>
)
}
}
class NewChildComponent extends Component {
constructor() {
super()
this.state = {
voting: 0,
}
}
onVoting = (e) => {
this.setState(prevState => ({
voteCount: e.target.name === "add" ? prevState.voteCount + 1 : prevState.voteCount - 1
}));
}
render () {
const {data} = this.props;
return (
<Item key={data.uniqueID}>
<text>
{data.subject}
{data.details}
</text>
<Votering>
<img src="" onClick={this.onVoting} name="add"/>
<div value={this.state.voteing}>{this.state.voteing}</div>
<img src="" onClick={this.onVoting} name="min"/>
</Votering>
</Item>
)
}
}
A little background on why you should not bind in your render function. https://medium.freecodecamp.org/why-arrow-functions-and-bind-in-reacts-render-are-problematic-f1c08b060e36
Here’s why: The parent component is passing down an arrow function on
props. Arrow functions are reallocated on every render (same story
with using bind). So although I’ve declared User.js as a
PureComponent, the arrow function in User’s parent causes the User
component to see a new function being sent in on props for all users.
So every user re-renders when any delete button is clicked. 👎
Also why you should not use an index as a key in React.
https://reactjs.org/docs/lists-and-keys.html
We don’t recommend using indexes for keys if the order of items may
change. This can negatively impact performance and may cause issues
with component state. Check out Robin Pokorny’s article for an
in-depth explanation on the negative impacts of using an index as a
key. If you choose not to assign an explicit key to list items then
React will default to using indexes as keys.
Here is an in-depth explanation about why keys are necessary if you’re
interested in learning more.
add a comment |
up vote
1
down vote
A few things I noticed unrelated to your question.
1) onVoting
should be bound in your constructor or use onVoting = () => { ..... }
2) in your render function you have onVote
instead of onVoting
On to your main question, in your state you are only maintaining one counter that is displayed and changed for all news elements. an easy way to get around this is to create a new react element for each news article that will handle the voting for each article.
class parent extends Component {
constructor(){
super()
this.state = {
news: null,
}
}
componentDidMount() {
// fetch data from api and minipulate as needed
this.setState({news: dataFromApi})
}
render() {
return (
<Content>
{
this.state.news.map((item, i)=>{
return (
<NewChildComponent data={item}/>
)
})
}
</Content>
)
}
}
class NewChildComponent extends Component {
constructor() {
super()
this.state = {
voting: 0,
}
}
onVoting = (e) => {
this.setState(prevState => ({
voteCount: e.target.name === "add" ? prevState.voteCount + 1 : prevState.voteCount - 1
}));
}
render () {
const {data} = this.props;
return (
<Item key={data.uniqueID}>
<text>
{data.subject}
{data.details}
</text>
<Votering>
<img src="" onClick={this.onVoting} name="add"/>
<div value={this.state.voteing}>{this.state.voteing}</div>
<img src="" onClick={this.onVoting} name="min"/>
</Votering>
</Item>
)
}
}
A little background on why you should not bind in your render function. https://medium.freecodecamp.org/why-arrow-functions-and-bind-in-reacts-render-are-problematic-f1c08b060e36
Here’s why: The parent component is passing down an arrow function on
props. Arrow functions are reallocated on every render (same story
with using bind). So although I’ve declared User.js as a
PureComponent, the arrow function in User’s parent causes the User
component to see a new function being sent in on props for all users.
So every user re-renders when any delete button is clicked. 👎
Also why you should not use an index as a key in React.
https://reactjs.org/docs/lists-and-keys.html
We don’t recommend using indexes for keys if the order of items may
change. This can negatively impact performance and may cause issues
with component state. Check out Robin Pokorny’s article for an
in-depth explanation on the negative impacts of using an index as a
key. If you choose not to assign an explicit key to list items then
React will default to using indexes as keys.
Here is an in-depth explanation about why keys are necessary if you’re
interested in learning more.
add a comment |
up vote
1
down vote
up vote
1
down vote
A few things I noticed unrelated to your question.
1) onVoting
should be bound in your constructor or use onVoting = () => { ..... }
2) in your render function you have onVote
instead of onVoting
On to your main question, in your state you are only maintaining one counter that is displayed and changed for all news elements. an easy way to get around this is to create a new react element for each news article that will handle the voting for each article.
class parent extends Component {
constructor(){
super()
this.state = {
news: null,
}
}
componentDidMount() {
// fetch data from api and minipulate as needed
this.setState({news: dataFromApi})
}
render() {
return (
<Content>
{
this.state.news.map((item, i)=>{
return (
<NewChildComponent data={item}/>
)
})
}
</Content>
)
}
}
class NewChildComponent extends Component {
constructor() {
super()
this.state = {
voting: 0,
}
}
onVoting = (e) => {
this.setState(prevState => ({
voteCount: e.target.name === "add" ? prevState.voteCount + 1 : prevState.voteCount - 1
}));
}
render () {
const {data} = this.props;
return (
<Item key={data.uniqueID}>
<text>
{data.subject}
{data.details}
</text>
<Votering>
<img src="" onClick={this.onVoting} name="add"/>
<div value={this.state.voteing}>{this.state.voteing}</div>
<img src="" onClick={this.onVoting} name="min"/>
</Votering>
</Item>
)
}
}
A little background on why you should not bind in your render function. https://medium.freecodecamp.org/why-arrow-functions-and-bind-in-reacts-render-are-problematic-f1c08b060e36
Here’s why: The parent component is passing down an arrow function on
props. Arrow functions are reallocated on every render (same story
with using bind). So although I’ve declared User.js as a
PureComponent, the arrow function in User’s parent causes the User
component to see a new function being sent in on props for all users.
So every user re-renders when any delete button is clicked. 👎
Also why you should not use an index as a key in React.
https://reactjs.org/docs/lists-and-keys.html
We don’t recommend using indexes for keys if the order of items may
change. This can negatively impact performance and may cause issues
with component state. Check out Robin Pokorny’s article for an
in-depth explanation on the negative impacts of using an index as a
key. If you choose not to assign an explicit key to list items then
React will default to using indexes as keys.
Here is an in-depth explanation about why keys are necessary if you’re
interested in learning more.
A few things I noticed unrelated to your question.
1) onVoting
should be bound in your constructor or use onVoting = () => { ..... }
2) in your render function you have onVote
instead of onVoting
On to your main question, in your state you are only maintaining one counter that is displayed and changed for all news elements. an easy way to get around this is to create a new react element for each news article that will handle the voting for each article.
class parent extends Component {
constructor(){
super()
this.state = {
news: null,
}
}
componentDidMount() {
// fetch data from api and minipulate as needed
this.setState({news: dataFromApi})
}
render() {
return (
<Content>
{
this.state.news.map((item, i)=>{
return (
<NewChildComponent data={item}/>
)
})
}
</Content>
)
}
}
class NewChildComponent extends Component {
constructor() {
super()
this.state = {
voting: 0,
}
}
onVoting = (e) => {
this.setState(prevState => ({
voteCount: e.target.name === "add" ? prevState.voteCount + 1 : prevState.voteCount - 1
}));
}
render () {
const {data} = this.props;
return (
<Item key={data.uniqueID}>
<text>
{data.subject}
{data.details}
</text>
<Votering>
<img src="" onClick={this.onVoting} name="add"/>
<div value={this.state.voteing}>{this.state.voteing}</div>
<img src="" onClick={this.onVoting} name="min"/>
</Votering>
</Item>
)
}
}
A little background on why you should not bind in your render function. https://medium.freecodecamp.org/why-arrow-functions-and-bind-in-reacts-render-are-problematic-f1c08b060e36
Here’s why: The parent component is passing down an arrow function on
props. Arrow functions are reallocated on every render (same story
with using bind). So although I’ve declared User.js as a
PureComponent, the arrow function in User’s parent causes the User
component to see a new function being sent in on props for all users.
So every user re-renders when any delete button is clicked. 👎
Also why you should not use an index as a key in React.
https://reactjs.org/docs/lists-and-keys.html
We don’t recommend using indexes for keys if the order of items may
change. This can negatively impact performance and may cause issues
with component state. Check out Robin Pokorny’s article for an
in-depth explanation on the negative impacts of using an index as a
key. If you choose not to assign an explicit key to list items then
React will default to using indexes as keys.
Here is an in-depth explanation about why keys are necessary if you’re
interested in learning more.
edited Nov 19 at 23:42
answered Nov 19 at 23:31
joshua fermin
384111
384111
add a comment |
add a comment |
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Some of your past answers have not been well-received, and you're in danger of being blocked from answering.
Please pay close attention to the following guidance:
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53383864%2freact-counter-for-each-item-of-list%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
could I get a sample of what the news array looks like?
– joshua fermin
Nov 19 at 23:06
I'm fetch data from api so is the link of this api will be enough for you? @joshua fermin
– GNDevs
Nov 19 at 23:08
The issue is that
voteing
is the only thing you're tracking, a single value in the state, that you're sharing between all iterations of yourmap
. Also, it's just "voting", no e– AnonymousSB
Nov 19 at 23:18