Reputation: 181
My question really simple: how do I create different instances of a React component?
I am doing an exercise in which you have to create a voting system: each component has its own quantity of votes.
The issue that I am having is that each component share the same number of votes, instead of being separate.
Here is the code:
import React, { useState } from 'react';
import ReactDOM from 'react-dom';
const Anecdote = ({text}) =>
{
const [votes, setVotes] = useState(0);
return (
<React.Fragment>
<p>{text}</p>
<p>Votes: {votes}</p>
<button onClick={() => setVotes(votes + 1)}>vote</button>
</React.Fragment>
)
}
const App = (props) =>
{
const [selected, setSelected] = useState(0);
function randomizeAnecdote(){
setSelected(Math.floor(Math.random() * anecdotes.length));
}
return (
<div>
{props.anecdotes[selected]}
<br/>
<button onClick={() => randomizeAnecdote()}>press</button>
</div>
)
}
const anecdotes = [
React.createElement(Anecdote, {text:'If it hurts, do it more often'}),
React.createElement(Anecdote, {text:'Adding manpower to a late software project makes it later!'}),
React.createElement(Anecdote, {text:'The first 90 percent of the code accounts for the first 90 percent of the development time...The remaining 10 percent of the code accounts for the other 90 percent of the development time.'}),
React.createElement(Anecdote, {text:'Any fool can write code that a computer can understand. Good programmers write code that humans can understand.'}),
React.createElement(Anecdote, {text:'Premature optimization is the root of all evil.'}),
React.createElement(Anecdote, {text:'Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it.'}),
]
ReactDOM.render(
<App anecdotes={anecdotes} />,
document.getElementById('root')
)
Basically, the function randomizeAnecdote()
chooses a random anecdote to be displayed with its own text. However, even when displaying another anecdote, the votes don't change.
As an example, if one anecdote has 10 votes and I press the button to randomize, the 10 votes stay there.
How can I make it so that votes
is unique to each element?
Upvotes: 2
Views: 441
Reputation: 1252
It seems a risky way to handle the count: I would never rely on the state of a "temporary" component for something important, since it makes things difficult for both persistency and tracking. I'm not familiar with useState
, but obviously there is something wrong with the Javascript closures.
I'd separate data and components (you have together now), keeping track of the count in the higher component possible (App, in your case, if you switch to redux it simplifies things) and creating the Anecdote on the fly. That would be an easier to manage option, imho.
If I would write the code, I'd tackle it differently. That is subjective, so don't take it as correct or wrong (I don't call myself an expert at all), but I'll put some comments for my thoughts.
import React from 'react';
import ReactDOM from 'react-dom';
// Anecdote is simple, there is no state, only rendering
// <> is a shortcut for <React.Fragment>, can't use in StackOverflow
const Anecdote = ({text, votes, incVotes}) =>
<React.Fragment>
<p>{text}</p>
<p>Votes: {votes}</p>
<button onClick={() => incVotes()}>vote</button>
</React.Fragment>
// Data and components are separate, I don't merge them
const anecdotes = [
'If it hurts, do it more often',
'Adding manpower to a late software project makes it later!',
'The first 90 percent of the code accounts for the first 90 percent of the development time...The remaining 10 percent of the code accounts for the other 90 percent of the development time.',
'Any fool can write code that a computer can understand. Good programmers write code that humans can understand.',
'Premature optimization is the root of all evil.',
'Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it.',
]
// I'd go for a standard declaration of class, at least for the App
class App extends React.Component {
// I'm not familiar with useState (my bad), so here a classic init for the state
// (very verbose, I know)
constructor(props) {
super(props);
// Bogus: it starts with 0, can be fixed obvs
this.state = { selected: 0, votesCount: props.anecdotes.map(() => 0) };
}
// Your function, now external, I find it more readable. It could be improved.
randomizeAnecdote() {
const selected = Math.floor(Math.random() * anecdotes.length);
setState({ selected });
}
// I'd use the callback for the state, in case multiple click occurs and
// React groups the calls.
// Note that I copy the array, this will simplify the transition to redux.
// Using "immutable" behaviour is a good habit and simplifies debug.
incVotes() {
this.setState(prevState => {
const votesCount = [...prevState.votesCount];
votesCount[prevState.selected]++;
return({ ...prevState, votesCount });
});
}
// Much simpler render, there is no more array of Anecdote
render() {
return (
<div>
<Anecdote
text={this.props.anecdotes[selected]}
votes={this.state.votesCount[selected]}
incVotes={() => this.incVotes()}
/>
<br/>
<button onClick={() => this.randomizeAnecdote()}>press</button>
</div>
);
}
}
ReactDOM.render(
<App anecdotes={anecdotes} />,
document.getElementById('root')
)
It might not reply to your answer (because I don't get which closure exactly is broken), but with an approach like above it should be easier to debug and maintain.
Upvotes: 1
Reputation: 24945
To reset the vote, you can listen on text
in useEffect
and whenever its changed, set vote to 0.
useEffect(() => {
setVotes(0)
}, [ text ])
Also, while testing I found an issue that random value is the same as previous value. So for that, you can use following hack:
function randomizeAnecdote(){
let randomValue = Math.floor(Math.random() * anecdotes.length);
randomValue = (randomValue === selected ? randomValue + 1 : randomValue) % anecdotes.length;
setSelected(randomValue);
}
Following is a sample code:
Note it addresses following things:
vote
count on new text.const { useState, useEffect } = React;
const Anecdote = ({text}) => {
const [votes, setVotes] = useState(0);
useEffect(() => {
setVotes(0)
}, [ text ])
return (
<React.Fragment>
<p>{text}</p>
<p>Votes: {votes}</p>
<button onClick={() => setVotes(votes + 1)}>vote</button>
</React.Fragment>
)
}
const App = ({anecdotes}) => {
const [selected, setSelected] = useState(0);
function randomizeAnecdote(){
let randomValue = Math.floor(Math.random() * anecdotes.length);
randomValue = (randomValue === selected ? randomValue + 1 : randomValue) % anecdotes.length;
setSelected(randomValue);
}
return (
<div>
<Anecdote text={ anecdotes[selected] } />
<br/>
<button onClick={() => randomizeAnecdote()}>press</button>
</div>
)
}
const anecdotes = [
'If it hurts, do it more often',
'Adding manpower to a late software project makes it later!',
'The first 90 percent of the code accounts for the first 90 percent of the development time...The remaining 10 percent of the code accounts for the other 90 percent of the development time.',
'Any fool can write code that a computer can understand. Good programmers write code that humans can understand.',
'Premature optimization is the root of all evil.',
'Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it.',
]
ReactDOM.render(
<App anecdotes={anecdotes} />,
document.getElementById('root')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script>
<div id='root' />
Updated code to maintain the count:
The reason its resetting to 0 is because the useEffect
is setting votes to 0
on change of text. If you need to maintain count, you will have to maintain a complex state.
In following sample, state is of type:
[ key: string ]: number
where key
is the text and value is the count.
In ideal env, I would create a redux store that would maintain both values in more detailed fashion. But for sample, you can create a map<text, vote>
and use it to display/maintain count.
const { useState, useEffect } = React;
const Anecdote = ({text}) => {
const [ myState, setMyState ] = useState({})
useEffect(() => {
if ( !myState[ text ] ) {
const state = { ...myState }
state[ text ] = 0;
setMyState(state);
}
}, [ text ])
const incrVote = () => {
const state = { ...myState }
state[ text ] = (state[ text ] || 0) + 1;
setMyState(state);
}
return (
<React.Fragment>
<p>{text}</p>
<p>Votes: {myState[ text ] || 0}</p>
<button onClick={incrVote}>vote</button>
</React.Fragment>
)
}
const App = ({anecdotes}) => {
const [selected, setSelected] = useState(0);
function randomizeAnecdote(){
let randomValue = Math.floor(Math.random() * anecdotes.length);
randomValue = (randomValue === selected ? randomValue + 1 : randomValue) % anecdotes.length;
setSelected(randomValue);
}
return (
<div>
<Anecdote text={ anecdotes[selected] } />
<br/>
<button onClick={() => randomizeAnecdote()}>press</button>
</div>
)
}
const anecdotes = [
'If it hurts, do it more often',
'Adding manpower to a late software project makes it later!',
'The first 90 percent of the code accounts for the first 90 percent of the development time...The remaining 10 percent of the code accounts for the other 90 percent of the development time.',
'Any fool can write code that a computer can understand. Good programmers write code that humans can understand.',
'Premature optimization is the root of all evil.',
'Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it.',
]
ReactDOM.render(
<App anecdotes={anecdotes} />,
document.getElementById('root')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script>
<div id='root' />
Upvotes: 1